Uploaded by Hercules Souza

intro to cbe computation

advertisement
Introduction to Chemical Engineering
Computation with MATLAB
®
Andrew S. Paluch, PhD
Department of Chemical, Paper and Biomedical Engineering
Miami University
Oxford, Ohio USA
PaluchAS@MiamiOH.edu
December 3, 2021
The present text was inspired by and is a derivative of the text: “Physical Modeling in MATLAB® ”, version
1.1.8, by Allen B. Downey. The latest version of the book is available at http://greenteapress.com/wp/
physical-modeling-in-matlab/ and is published by Green Tea Press.
Permission is granted to copy, distribute, and/or modify this document under the terms of the Creative
Commons Attribution-NonCommercial 3.0 Unported License, which is available at http://creativecommons.
org/licenses/by-nc/3.0/. This is the license under which “Physical Modeling in MATLAB® ” was originally
published.
Preface
When I joined the faculty of the Department of Chemical, Paper and Biomedical
Engineering at Miami University in Fall 2013, I was tasked with developing a computational methods course for our chemical and bio- engineering majors. I was
encouraged to design the course around the use of MATLAB® ; our bioengineering
students take a sequence of courses in electrical engineering which extensively
use MATLAB® , and a comparable course taught in mechanical and manufacturing
engineering also uses MATLAB® . The course created was CPB 324 Chemical and
Bio- Engineering Computation and Statistics. With the time required for the new
course to be approved by the university, and then to become a required course
in our chemical and bio- engineering curriculum (which also requires university
approval), I did not teach my first section of the course until Spring 2017.
During my time at Miami University I have taught a total of 6 core chemical
courses: fluid mechanics, mass transfer, thermodynamics 1 and 2, reactor design,
and chemical engineering process design. I also work alongside undergraduate
students in my computational thermodynamics laboratory, have attended workshops on e-learning and “open” textbooks, and taught a class at Universidade
Federal Fluminense (UFF), in Niterói, Rio de Janerio, Brazil. All of these experiences have shaped the current version of this course, which differs from my
original vision. Every semester I teach the course I continue to modify the content
in an attempt to optimize student success. Beginning with the first offering of this
course, I have kept four major concepts in mind:
1. Assume that the students in the class have never programmed before, and
have no previous experience with MATLAB® . While students at Miami
University are introduced to MATLAB® during the their first year in CPB 102
Introduction to Chemical and Bio- Engineering, the introduction is brief
and students are too often overwhelmed with their first year studies such
that it is as if the encounter never happened.
2. It is not my goal to teach everything there is to know about MATLAB® , or
everything that will be encountered in the Chemical and Bio- Engineering
curriculum in a single semester. My goal is to teach MATLAB® so that my
students understand and appreciate it... and do not hate MATLAB® by the
iii
iv
end of the semester! By teaching less breadth, I can cover material in
greater depth, which I believe gives my students the skills and confidence
for additional self-study of new material. Material that I can add to this text
in the future. Also, for any material that I am unable to cover, the excellent
documentation packaged with MATLAB® will always be available too.
3. To create an “open” (free) textbook for the class. This format allows me to:
lower the barrier of education, allowing anyone and everyone to obtain
a copy; completely customize the text for class; and to create a resource
for future classes and future endeavors. Our students are asked to learn a
tremendous amount of material in a short period of time. While a year after
completing the course I can not expect them to remember everything, what
is important is they know where to turn for help. They will always have
access to the latest version of this text, which I plan to grow and improve
every semester.
4. And lastly, to teach the course material so that students are encouraged
to use MATLAB® in their future course studies. Too often engineers are
associated with making assumptions in order to solve problems. While this
may have been necessary fifty years ago, it need not necessarily be the case
today. With computational tools we may solve problems that we would
never imagine solving otherwise. Additionally, as a student, mathematical
modeling with MATLAB® can be an excellent learning tool. Rather than
just reading about a theory in a text, we can set-up a physical modeling,
and then readily perform virtual experiments; we can change parameters
and look at what the effect of each is. To encourage this, we will solve
problems in the course from upper level classes, where you need not know
the underlying theory, but can solve the problems using MATLAB® and can
further use MATLAB® to gain insight.
As mentioned, the course is designed assuming that the students in class have
never programmed before. This is different from most texts that use MATLAB® ,
which are aimed at readers who know how to program. As a result, the order of
presentation is unusual. The text starts with scalar values and works up to vectors
and matrices very gradually. This approach is good for beginning programmers,
because it is hard to understand composite objects until you understand basic
programming semantics. But there are problems:
• The MATLAB® documentation is written in terms of matrices, and so are the
error messages. To mitigate this problem, the book explains the necessary
vocabulary early and deciphers some of the messages that beginners find
confusing.
• Many of the examples in the first few chapters are not idiomatic MATLAB® .
This problem is addressed in the later chapters by translating the examples
into a more standard style.
v
The texts puts a lot of emphasis on functions, in part because they are an
important mechanism for controlling program complexity, and also because
they are useful for working with MATLAB® tools; we will look at fzero, fsolve,
ode45, and integral in this text. However, they are additionally central to the
use of many other tools students will likely encounter in their future studies; for
example, fminbnd comes up in my thermodynamics course, and after completing
this course I find students are readily able to use it after consulting the excellent
documentation provided by MATLAB® .
In teaching the course I have to be selective of the material covered. Typically,
the first third of the course can be thought of as an introduction to computer
programming. While we will use MATLAB® , the logic is equally applicable to other
languages and programs. I find that students struggle most with this material, so
we spend ample time working through examples. In last two thirds of the course
we then turn and look at some of MATLAB’s® built-in functionalities. We begin
with root-finding and systems of equations, and then work our way up to systems
of ordinary differential equations. These are skills I wish my class had when I
taught our course in reactor design. The text then moves to interpolation, which
results naturally as a complement to numerically solving ordinary differential
equations. We then end with numerical integration. At present, this is all that I
can comfortably cover in a semester. However, recently this text has been adopted
by CPB 102 Introduction to Chemical and Bio- Engineering which teaches the first
few chapters. I hope that in the near future this will allow me to add additional
material on optimization, curve fitting, and symbolic calculations.
When a new numerical method is introduced, a brief amount of time will be
spent writing and using our own function. This text is not a text on numerical
methods. We will look at some basic numerical methods to help understand
how MATLAB’s® built-in functions work. While this is good to help students
appreciate MATLAB® , I find it invaluable to better understand the required inputs,
the outputs, and what can go wrong. Links will be provided to external resources
(typically Wikipedia and textbooks available in the Open Textbook Library) for
the reader interested in learning more.
While the text is formatted to be printed, I believe the electronic copy is
much better. You will notice that hyperlinking is used throughout the text. First,
the documentation provided by MATLAB® is invaluable. You will find that I link
to it throughout the text. I emphasize the documentation is invaluable, and I
encourage my students to consult it throughout the text. This not only improves
student learning of the topics covered in the text, but it helps students become
comfortable using the documentation in the future if they need to learn a new
feature of MATLAB® . Second, in the text I provide copies of all of my M-files.
Additionally, you will find that in the M-file caption, I link to a digital copy stored
in my Google Drive account that you may download. Third, links are provided
throughout to screen casts on my YouTube channel. I would encourage everyone
using this text to subscribe. Initially, my screen casts will be of solutions to select
examples and topics that frequently cause confusion, but I will continue to grow
vi
the channel and improve the quality of the screen casts. You may notice my
solutions in the screen casts differ slightly from the text; in the screen casts I
am genuinely solving the problems and offer commentary throughout. When
applicable, the description of the YouTube video contains a link the my Google
Drive account to download the M-file created in the video. At present I have
not updated the text solutions to be identical to the screen casts to provide an
additional example.
At present, the text is only available as a PDF document. I have spent a great
amount of time working on an HTML version. However, in the end, I believe the
PDF is better. It allows me to readily share the text with anyone via a single email.
And I believe the use of hyperlinking allows the text to come alive, similar to what
the user experience with the HTML version might be. Before getting started have
a look at my screen cast demonstrating the use of hyperlinking.
Additionally in the spirit of free and accessible, I plan to evolve the text from
exclusively using MATLAB® to also use freely available software. I will start with
GNU Octave, which is what I actually use in my research group. At present, GNU
Octave can be used throughout most of the text. The exception is I present the
option of using nested functions, which GNU Octave does not support. This
appears later in the text when solving systems of second order initial value ordinary differential equations. I delay the introduction of nested functions because
they often cause confusion initially because I frequently repeat the message the
functions have their own workspace. However, for the trajectory problems we
solve, the use of nested functions greatly simplifies our programs as compared to
attempting to use anonymous functions.
If you have any suggestions or comments, please let me know. If you are
interested in helping to develop the text and/or the YouTube channel, I would
love to hear from you. If you encounter a problem in another course you would
like to solve using MATLAB® let me know and material can be added to the text.
Please also let me know if you would like to contribute the material to the text.
Please also know that many excellent (and freely available) resources are
available to you to help you learn MATLAB® . You should bookmark the online
documentation. MathWorks® , the fine folks that develop MATLAB® , also have
an “Academia” section on their website worth checking out. You will find many
excellent MATLAB® tutorials available. You will also find that many other resources
are available. For example, a free “Introduction to Programming with MATLAB”
course is offered by Vanderbilt University on coursera. The course covers material
from the first third of this course, and a series of very good YouTube videos are
available. Many others exist, this is just an example.
Lastly, I would like to acknowledge and thank Professor Allen B. Downey,
Professor of Computer Science at Olin College, who has written many “open”
textbooks. This text was inspired by and is a derivative of his textbook “Physical
Modeling in MATLAB® ”, version 1.1.8, which is published under the terms of the
Creative Commons Attribution-NonCommercial 3.0 Unported License. The text
is additionally available in the Open Textbook Library, which I would encourage
vii
you to visit.
Contents
1
2
3
Variables and values
1.1 A glorified calculator . . . . . . .
1.2 Math functions . . . . . . . . . . .
1.3 Documentation . . . . . . . . . .
1.4 Variables . . . . . . . . . . . . . .
1.5 Assignment statements . . . . . .
1.6 Why variables? . . . . . . . . . . .
1.7 Errors . . . . . . . . . . . . . . . .
1.8 Floating-point arithmetic . . . . .
1.9 Comments . . . . . . . . . . . . .
1.10 Examples . . . . . . . . . . . . . .
1.11 Chemical Engineering Examples
1.12 MATLAB Online . . . . . . . . . .
1.13 Glossary . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
4
5
6
7
7
8
10
12
13
15
20
21
Scripts
2.1 M-files . . . . . . . . . . . .
2.2 Why scripts? . . . . . . . .
2.3 The workspace . . . . . . .
2.4 More errors . . . . . . . . .
2.5 Pre- and post-conditions .
2.6 Assignment and equality .
2.7 Updating variables . . . .
2.8 Incremental development
2.9 Unit testing . . . . . . . . .
2.10 Kinds of error . . . . . . . .
2.11 Absolute and relative error
2.12 CPB Examples . . . . . . .
2.13 Glossary . . . . . . . . . . .
2.14 Exercises . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
23
23
27
27
29
29
30
34
35
36
37
38
38
44
45
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
for loops and basic plotting
3.1 for loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2
3.3
plotting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Sequences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ix
49
49
53
58
x
CONTENTS
3.4
3.5
3.6
3.7
3.8
3.9
3.10
4
5
Series . . . . . . . . . . .
Generalization . . . . . .
Examples . . . . . . . . .
CPB Examples . . . . . .
Creating multiple figures
Glossary . . . . . . . . . .
Exercises . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
59
61
63
69
73
76
77
Logic and vectors
4.1 Checking preconditions
4.2 if . . . . . . . . . . . . .
4.3 Relational operators . . .
4.4 Logical operators . . . .
4.5 exist . . . . . . . . . . .
4.6 Vectors . . . . . . . . . . .
4.7 Vector arithmetic . . . .
4.8 Everything is a matrix . .
4.9 Indices . . . . . . . . . . .
4.10 Indexing errors . . . . . .
4.11 Vectors and sequences .
4.12 Sizing vectors . . . . . . .
4.13 Creating vectors . . . . .
4.14 Plotting vectors . . . . .
4.15 Reduce . . . . . . . . . .
4.16 Apply . . . . . . . . . . .
4.17 Search . . . . . . . . . . .
4.18 Spoiling the fun . . . . .
4.19 Strings . . . . . . . . . . .
4.20 Examples . . . . . . . . .
4.21 CPB Examples . . . . . .
4.22 Glossary . . . . . . . . . .
4.23 Exercises . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
81
81
82
84
85
86
88
89
89
91
92
93
96
97
100
102
103
104
107
108
108
118
132
133
Functions
5.1 Name Collisions . . . . . . . . . . . . .
5.2 Functions . . . . . . . . . . . . . . . . .
5.3 Documentation . . . . . . . . . . . . .
5.4 Function names . . . . . . . . . . . . .
5.5 Multiple input variables . . . . . . . .
5.6 Logical functions . . . . . . . . . . . . .
5.7 An incremental development example
5.8 Nested loops . . . . . . . . . . . . . . .
5.9 Conditions and flags . . . . . . . . . .
5.10 Encapsulation and generalization . . .
5.11 A misstep . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
135
135
136
139
140
143
146
149
149
151
153
154
CONTENTS
5.12
5.13
5.14
5.15
5.16
5.17
6
7
xi
continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Mechanism and leap of faith
Examples . . . . . . . . . . .
CPB Examples . . . . . . . .
Glossary . . . . . . . . . . . .
Exercises . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Functions of vectors
6.1 Functions and files . . . . . . . . .
6.2 Vectors as input variables . . . . . .
6.3 Vectors as output variables . . . . .
6.4 Multiple/optional output variables
6.5 Vectorizing your functions . . . . .
6.6 Sums and differences . . . . . . . .
6.7 Products and ratios . . . . . . . . .
6.8 Existential quantification . . . . . .
6.9 Universal quantification . . . . . .
6.10 Logical vectors . . . . . . . . . . . .
6.11 CPB Examples . . . . . . . . . . . .
6.12 Glossary . . . . . . . . . . . . . . . .
6.13 Exercises . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Zero-finding
7.1 Why functions? . . . . . . . . . . . . . . . . . . . . . .
7.2 Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.3 A note on notation . . . . . . . . . . . . . . . . . . . .
7.4 Nonlinear equations . . . . . . . . . . . . . . . . . .
7.5 Zero-finding . . . . . . . . . . . . . . . . . . . . . . .
7.6 fzero . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.7 What could go wrong? . . . . . . . . . . . . . . . . . .
7.8 Finding an initial guess . . . . . . . . . . . . . . . . .
7.9 More name collisions . . . . . . . . . . . . . . . . . .
7.10 Debugging in four acts . . . . . . . . . . . . . . . . .
7.11 Examples . . . . . . . . . . . . . . . . . . . . . . . . .
7.12 Functions: what we’ve done so far . . . . . . . . . .
7.13 Anonymous function . . . . . . . . . . . . . . . . . .
7.14 What could go wrong? . . . . . . . . . . . . . . . . . .
7.15 Saving anonymous functions . . . . . . . . . . . . .
7.16 Revisiting the duck . . . . . . . . . . . . . . . . . . .
7.17 Numerical methods: zero-finding . . . . . . . . . . .
7.17.1 The bisection method . . . . . . . . . . . . .
7.17.2 The secant (or linear interpolation) method
7.18 roots . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.19 Examples . . . . . . . . . . . . . . . . . . . . . . . . .
7.20 CPB Examples . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
158
159
160
169
170
.
.
.
.
.
.
.
.
.
.
.
.
.
173
173
177
178
180
183
185
187
190
191
192
197
200
201
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
203
203
204
204
205
206
207
209
211
214
216
217
221
222
225
226
227
229
229
237
240
242
247
xii
CONTENTS
8
9
7.21 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.22 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
249
251
Systems of Equations
8.1 Matrices . . . . . . . . . . . . . . . . .
8.2 Row and column vectors . . . . . . .
8.3 The transpose operator . . . . . . . .
8.4 Zero-finding: what we’ve done so far
8.5 System of equations . . . . . . . . . .
8.6 What can go wrong? . . . . . . . . . .
8.7 Examples . . . . . . . . . . . . . . . .
8.8 One final fsolve note . . . . . . . . .
8.9 System of linear equations . . . . . .
8.10 Gaussian elimination . . . . . . . . .
8.11 What could go wrong? . . . . . . . . .
8.12 Left division . . . . . . . . . . . . . .
8.13 Application: Raoult’s Law . . . . . . .
8.13.1 Specifying T and x 1 . . . . .
8.13.2 Specifying p and x 1 . . . . .
8.14 Application: CSTR . . . . . . . . . . .
8.14.1 CSTR Basics . . . . . . . . . .
8.14.2 A single reaction example . .
8.15 Glossary . . . . . . . . . . . . . . . . .
8.16 Exercises . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
257
257
260
261
262
262
265
268
273
274
274
276
277
279
280
285
292
292
294
303
304
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
313
313
315
316
316
319
320
321
323
325
331
335
349
349
351
351
352
353
354
Ordinary Differential Equations
9.1 Differential equations . . . .
9.2 Euler’s method . . . . . . . .
9.3 Another note on notation . .
9.4 ode45 . . . . . . . . . . . . .
9.5 Multiple output variables . .
9.6 Analytic or numerical? . . .
9.7 What can go wrong? . . . . .
9.8 Stiffness . . . . . . . . . . . .
9.9 Examples . . . . . . . . . . .
9.10 Euler in action! . . . . . . . .
9.11 Return of fzero . . . . . . .
9.12 Isothermal Batch Reactors .
9.12.1 Batch Reactor Basics
9.12.2 Useful Assumption .
9.12.3 Note on Units . . . .
9.12.4 Final Note . . . . . .
9.13 Glossary . . . . . . . . . . . .
9.14 Exercises . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
CONTENTS
xiii
10 Systems of ODEs
10.1 Lotka-Voltera . . . . . . . . . . . . . . . . . . . .
10.2 What can go wrong? . . . . . . . . . . . . . . . .
10.3 Output matrices . . . . . . . . . . . . . . . . . .
10.4 Examples . . . . . . . . . . . . . . . . . . . . . .
10.5 Euler returns! . . . . . . . . . . . . . . . . . . . .
10.6 Isothermal Batch Reactors: Multiple Reactions
10.7 Glossary . . . . . . . . . . . . . . . . . . . . . . .
10.8 Exercises . . . . . . . . . . . . . . . . . . . . . .
11 Second-order systems
11.1 Nested functions . . . . . . . . . .
11.1.1 Application fzero . . . .
11.2 Newtonian motion . . . . . . . .
11.3 Freefall . . . . . . . . . . . . . . .
11.4 Air resistance . . . . . . . . . . . .
11.5 Parachute! . . . . . . . . . . . . . .
11.6 Two dimensions . . . . . . . . . .
11.6.1 Flying in vacuum . . . . .
11.6.2 Flying with air resistance
11.7 What could go wrong? . . . . . . .
11.8 ODE Events . . . . . . . . . . . . .
11.9 What could go wrong? . . . . . . .
11.10 Glossary . . . . . . . . . . . . . . .
11.11 Exercises . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
355
355
358
359
360
372
375
378
378
.
.
.
.
.
.
.
.
.
.
.
.
.
.
383
383
387
388
388
397
405
409
409
419
427
429
440
441
441
12 Interpolation
443
12.1 Discrete and continuous maps . . . . . . . . . . . . . . . . . . . . 443
12.2 Interpolation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
12.3 Interpolating the inverse function . . . . . . . . . . . . . . . . . . 448
12.4 Creating a function . . . . . . . . . . . . . . . . . . . . . . . . . . . 451
12.5 Field mice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453
12.6 Good results come from good users . . . . . . . . . . . . . . . . . . 457
12.7 Fun with tabulated pure component VLE data (i.e., the saturated
steam tables) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
12.7.1 Downloading the saturated steam tables . . . . . . . . . . 466
12.7.2 dlmread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468
12.7.3 interp1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
12.7.4 Plotting phase diagrams . . . . . . . . . . . . . . . . . . . . 476
12.8 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
12.9 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481
13 Numerical Integration
483
13.1 Integration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 483
13.2 Numerical Integration . . . . . . . . . . . . . . . . . . . . . . . . . 484
xiv
CONTENTS
13.2.1 integral . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.2.2 Working with Tabulated Data . . . . . . . . . . . . . . . . .
13.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14 Monte Carlo
14.1 Random number generator basics . . . . .
14.2 Back to π . . . . . . . . . . . . . . . . . . . .
14.3 Evaluating Integrals in General . . . . . . .
14.4 Simplifying and Generalizing the Algorithm
14.5 Exercises . . . . . . . . . . . . . . . . . . . .
488
490
492
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
493
495
497
502
504
508
15 An Introduction to Symbolic Calculations
15.1 Some Symbolic Variable Basics . . . . . . . . . .
15.2 Limits . . . . . . . . . . . . . . . . . . . . . . . . .
15.2.1 Basic Limits . . . . . . . . . . . . . . . . .
15.2.2 Intermediate Forms and L’Hôpital’s Rule
15.2.3 Directional Limits . . . . . . . . . . . . .
15.2.4 Limits and Derivatives . . . . . . . . . . .
15.3 Derivatives . . . . . . . . . . . . . . . . . . . . . .
15.4 Indefinite and Definite Integrals . . . . . . . . . .
15.5 More fun with solve . . . . . . . . . . . . . . . .
15.6 Factor my polynomial please . . . . . . . . . . .
15.7 Solving systems of equations . . . . . . . . . . . .
15.8 Initial Value Ordinary Differential Equations . .
15.9 Boundary Value Problems . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
509
509
512
512
514
514
515
516
517
519
521
521
522
525
.
.
.
.
.
.
.
.
.
.
Appendix A while loops
529
A.1 Loops: what we’ve done so far . . . . . . . . . . . . . . . . . . . . . 529
A.2 while loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 529
Chapter
1
Variables and values
In Chapters 1 to 6 we begin with some MATLAB “basics.” The goal is the start by
building a strong foundational knowledge. From there, the MATLAB world is your
oyster. By the end of this chapter you will be able to:
• Demonstrate the ability to perform basic mathematical operations in the
MATLAB command window
• Recall how to assign variables, order of operations, and how to search the
documentation
• Apply MATLAB to solve basic engineering problems
t You may see different windows and/or a different window layout upon starting
MATLAB. The layout may
be modified using the large
Layout icon on the Home
menu tab. You can also click
on the windows and drag
them to re-arrange.
t In teaching this course I
have used MATLAB versions R2013a and R2018a.
In going from R2013a to
R2018a, the only changes I
have observed are to error
1.1 A glorified calculator
messages, warnings about
features that will become
At heart, MATLAB is a glorified calculator. When you start MATLAB you will
obsolete in the future, and
see a window entitled MATLAB R2018a that contains smaller windows entitled
the options used by a few
Current Folder , Workspace , Command History and Command Window . The Command Window of MATLAB’s built-in functions, all of which will not
runs the MATLAB interpreter, which allows you to type MATLAB commands,
impact this course. Please
then executes them and prints the result.
alert me if you discover
this is not the case. A nice
summary of the MATLAB
release history is provided
on the MATLAB Wikipedia
page.
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
1
2
Chapter 1 Variables and values
Figure 1.1 MATLAB R2018a GUI.
t If you are familiar using
a bash shell on Linux or
Apple OS (i.e., the default
terminal), then you will
notice that those commands work here in the
Command Window . For example here, pressing
will
recall your previous commands. If you were to keep
pressing
, it will keep going through your previous
commands sequentially.
Initially, the Command Window contains a welcome message: “New to MATLAB? See resouces for Getting Started.” Getting Started is a hyperlink, give it a
click. You will find that the documentation provided by MATLAB is excellent!
When learning something new, please always consult the documentation. I will
try to link to appropriate pages throughout the text. The documentation pages
are available in your stand-alone MATLAB on your computer, and the fine folks at
MathWorks also make it freely available online.
The welcome message is followed by a chevron:
>>
which is the MATLAB prompt; that is, this symbol prompts you to enter a command.
The simplest kind of command is a mathematical expression, which is made
up of operands (like numbers, for example) and operators (like the plus sign, +).
If you type an expression and then press Enter (or
), MATLAB evaluates the
expression and prints the result.
>> 2 + 1
ans = 3
Just to be clear: in the example above, MATLAB printed »; I typed 2 + 1 and then
hit Enter , and MATLAB printed ans = 3. And when I say “printed,” I really mean
“displayed on the screen,” which might be confusing, but it’s the way MATLAB folk
talk (or computer scientists in general).
An expression can contain any number of operators and operands. You don’t
have to put spaces between them; some people do and some people don’t.
>> 1+2+3+4+5+6+7+8+9
1.1 A glorified calculator
3
ans = 45
Speaking of spaces, you might have noticed that MATLAB puts some space between the expression and ans =, and between ans = and the result. In my examples I will leave it out to save paper.
The other arithmetic operators are pretty much what you would expect. Subtraction is denoted by a minus sign, –; multiplication by an asterisk, * (sometimes
pronounced “splat”); division by a forward slash /.
>> 2*3 - 4/5
ans = 5.2000
The order of operations is what you would expect from basic algebra: parentheses, exponents, multiplication and division, and addition and subtraction.
In the United States the acronym “PEMDAS” or the mnemonic phrase “Please
Excuse My Dear Aunt Sally” is commonly used to recall the order of operations.
Exponentiation happens before multiplication and division, followed by addition
and subtraction; if you want to override the order of operations, you can use
parentheses.
>> 2 * (3-4) / 5
ans = -0.4000
When I added the parentheses I also changed the spacing to make the grouping of
operands clearer to a human reader. This is the first of many style guidelines I will
recommend for making your programs easier to read. Style doesn’t change what
the program does; the MATLAB interpreter doesn’t check for style. But human
readers do, and the most important human who will read your code is you.
And that brings us to the First Theorem of debugging:
Readable code is debuggable code.
It is worth spending time to make your code pretty; it will save you time debugging!
The other common operator is exponentiation, which uses the ^ symbol,
sometimes pronounced “carat” or “hat”. So 2 raised to the 16th power is
>> 2^16
ans = 65536
Again, exponentiation happens before multiplication and division, followed by
addition and subtraction; if you want to override the order of operations, you can
use parentheses.
t Use parentheses, do NOT
use brackets. Brackets
mean something different to MATLAB, and will be
used later when working
with vectors and matrices.
t When in doubt of the order
of operations, add parentheses. This includes parentheses around exponents.
I sometimes do this out of
habit. If I wanted to raise 2
to the power of 10+6, I better put 10+6 in parentheses.
Otherwise it will be 2 raised
to the power of 10, then the
result of that calculation
plus 6.
t There are so many possible function calls you may
find it difficult to remember
them all. If you can remember what the command
starts with, you can use
+
Tab + Tab (or
) to
try and auto-fill in the rest.
If there is more than one
possibility, a list of possible
commands will be shown. I
often find this useful for little details and spelling (e.g.,
sin or sine). (Again, this
is a bash shell command.)
Alternatively, search the
documentation.
4
Chapter 1 Variables and values
1.2 Math functions
MATLAB knows how to compute pretty much every math function you’ve heard
of. It knows all the trigonometric functions; here’s how you use them:
Figure 1.2 If you need to find a
command, use the “Search Documentation” text box in the top
right corner of the MATLAB window, or search the documentation
online.
>> sin(1)
ans = 0.8415
This command is an example of a function call. The name of the function is
sin, which is the usual abbreviation for the trigonometric sine. The value in
parentheses is called the argument. All the standard trig functions in MATLAB
work in radians. If you wish to work in degrees, you can: 1) covert from degrees to
radians with the conversion factor 180/π, or 2) you can add a “d” to the end of
the function. For example, while sin works in radians, sind works in degrees.
Some functions take more than one argument, in which case they are separated by commas. For example, atan2 computes the inverse tangent, which is
the angle in radians between the positive x-axis and the point with the given y
and x coordinates.
>> atan2(1,1)
ans = 0.7854
If that bit of trigonometry isn’t familiar to you, don’t worry about it. It’s just an
example of a function with multiple arguments.
MATLAB also provides exponential functions, like exp, which computes e
raised to the given power. So exp(1) is just e.
>> exp(1)
ans = 2.7183
t I emphasize that the function log computes the logarithm with base e; what
you likely think of as ln.
To compute a logarithm
with base 10, use the function log10. You might also
find the Change-of-Base
Formula useful.
The inverse of exp is log, which computes the logarithm with base e:
>> log(exp(3))
ans = 3
This example also demonstrates that function calls can be nested; that is, you can
use the result from one function as an argument for another.
More generally, you can use a function call as an operand in an expression.
>> sqrt(sin(0.5)^2 + cos(0.5)^2)
ans = 1
As you probably guessed, sqrt computes the square root.
There are lots of other math functions, but this is not meant to be a reference
1.3 Documentation
5
manual. To learn about other functions, you should read the documentation. In
fact, whenever you use a function for the first time, I would encourage you to read
the function’s documentation page.
1.3 Documentation
MATLAB comes packaged with two forms of documentation, help and doc.
The help command works from the Command Window ; just type help followed
by the name of a command.
t The blue underlined text is
a hyperlink. Give it a click.
Here, if you were to click
on asin, it is as if you had
typed help asin.
>> help sin
sin
Sine of argument in radians.
sin(X) is the sine of the elements of X.
See also asin, sind.
t If MATLAB does not sug-
Reference page for sin
Other functions named sin
Unfortunately, this documentation is not beginner-friendly.
One gotcha is that in previous versions of MATLAB1 , the name of the function
appears in the help page in capital letters, but if you type it like that in MATLAB,
you get an error:
>> SIN(1)
Undefined command/function 'SIN' for input arguments of type 'double'.
Did you mean:
>> sin(1)
Notice that MATLAB is smart and suspects the typographical error, providing you
with a new command prompt with the correct function. In the current version
of MATLAB, boldface is used instead of capital letters to highlight the function
name in the documentation, to help prevent you from making this mistake.
Another problem is that the help page uses vocabulary you don’t know yet.
For example, “the elements of X” won’t make sense until we get to vectors
and matrices a few chapters from now.
The doc pages are usually better. If you type doc sin, a browser appears
with more detailed information about the function, including examples of how to
use it. The examples often use vectors and arrays, so they may not make sense
1 With MATLAB R2015a, lowercase “sin” is used in the Command Window . However, if I use
MATLAB R2015a from my Linux terminal, a non-GUI version that you would likely encounter at a
high performance computing center, capital letters are used for “SIN”! So even with newer versions,
it appears that you still need to be careful.
gest a correction for a typographical error, you
may need to turn it on.
From the Home menu
tab, select Preferences
Command Window . Then
under Display , you will find
a box that you can check
to “Suggest corrections for
mistyped functions and
variables.”
6
Chapter 1 Variables and values
yet, but you can get a preview of what’s coming. Whenever you encounter a new
function, I would strongly encourage you to get in the habit of looking at the doc
page. Remember, you can also access the documentation pages online.
1.4 Variables
t Technically pi is a function,
not a variable. I only know
this because if we type doc
pi to bring up the documentation page, we find
“pi returns the floatingpoint number nearest the
value of π”. Since pi is a
function, it can be overridden and used as a variable
name. This also means pi is
not stored in our Workspace .
But for now you can ignore
all this and pretend it is a
variable.
t Note: you can’t use Greek
letters in MATLAB; when
translating math expressions with Greek letters, it
is common to write out the
name of the letter (assuming you know it).
t Just like pi, i and j are technically functions. From
the documentation page,
“Since both i and j are
functions, they can be
overridden and used as a
variable. This permits you
to use i or j as an index in
FOR loops, etc.”, which is a
common practice that we
will use later in the text. But
as with pi, for now you can
ignore this.
One of the features that makes MATLAB more powerful than a calculator is the
ability to give a name to a value. A named value is called a variable.
MATLAB comes with a few predefined variables. For example the name pi
refers to the mathematical quantity π, which is approximately
>> pi
ans = 3.1416
And if you do anything with complex numbers, you might find it convenient
that both i and j are predefined as the square root of −1.
You can use a variable name anywhere you can use a number; for example, as
an operand in an expression:
>> pi * 3^2
ans = 28.2743
or as an argument to a function:
>> sin(pi/2)
ans = 1
>> exp(i * pi)
ans = -1.0000 + 0.0000i
As the second example shows, many MATLAB functions work with complex
numbers. This example demonstrates Euler’s identity: e i π + 1 = 0.
Whenever you evaluate an expression, MATLAB assigns the result to a variable
named ans. You can use ans in a subsequent calculation as shorthand for “the
value of the previous expression”.
>> 3^2 + 4^2
ans = 25
>> sqrt(ans)
ans = 5
But keep in mind that the value of ans changes every time you evaluate an expression.
1.5 Assignment statements
7
1.5 Assignment statements
You can create your own variables, and give them values, with an assignment
statement. The assignment operator is the equals sign, =.
>> x = 6 * 7
x = 42
This example creates a new variable named x and assigns it the value of the
expression 6 * 7. MATLAB responds with the variable name and the computed
value.
In every assignment statement, the left side has to be a legal variable name.
The right side can be any expression, including function calls. Almost any sequence of lower and upper case letters is a legal variable name. The only legal
punctuation (non-letter) is the underscore, _. Numbers are fine, but the variable
name must begin with a letter. Spaces are not allowed, but this is where the
underscore is commonly used instead. Variable names are “case sensitive”, so x
and X are different variables.
t Note: The current value of
all assigned variables will be
shown in the Workspace
window, which is typically to the right of the
Command Window , unless
you have modified your
layout.
>> fibonacci0 = 1;
>> LENGTH = 10;
>> first_name = 'allen'
first_name = allen
The first two examples demonstrate the use of the semi-colon, which suppresses the output from a command. In this case MATLAB creates the variables
and assigns them values, but displays nothing. The third example demonstrates
that not everything in MATLAB is a number. A sequence of characters in single
quotes is a string.
Although i, j and pi are predefined, you are free to reassign them. It is
common to use i and j for other purposes, but it is probably not a good idea to
change the value of pi!
Figure 1.3 Workspace and History windows. Notice variables
are listed in alphabetical and
not chronological order in the
Workspace window.
t In general, I will reserve
1.6 Why variables?
The most common reasons to use variables are:
1. To avoid recomputing a value that is used repeatedly. For example, if you
are performing computations involving e, you might want to compute it
once and save the result.
>> e = exp(1)
e = 2.7183
variable names with uppercase letters for vectors and
matrices, and will use lowercase letters for scalars (i.e.,
everything else).
t Spaces are not allowed, so
I commonly use an _ in
place of where I might like
a space when naming a
variable. You will find the
same practice useful later
when you save script and
function files.
8
Chapter 1 Variables and values
t Two notes on breaking a
long expression onto multiple lines using ...: 1) It
must follow after an operand
(i.e., +, –, *, /). 2) In the MATLAB Command Window pressing Enter (or
) will cause
MATLAB to evaluate the line
and will not have the desired
effect of continuing on to
the next line. To do this, type
Shift + Enter .
2. To make the connection between the code and the underlying mathematics
more apparent. If you are computing the area of a circle, you might want to
use a variable named r which corresponds to the radius:
>> r = 3
r = 3
>> area = pi * r^2
area = 28.2743
That way your code resembles the familiar formula πr 2 .
3. To break a long computation into a sequence of steps. Suppose you are
evaluating a big, hairy expression like this:
>> ans = ((x - theta) * sqrt(2 * pi) * sigma) ^ -1 * ...
exp(-1/2 * (log(x - theta) - zeta)^2 / sigma^2)
You can use an ellipsis to break the expression into multiple lines. Just
type ... at the end of the first line and continue on the next. And in the
future if you forget about the use of an ellipsis, MATLAB conveniently has a
documentation page titled: “Continue Long Statements on Multiple Lines”.
(In case you haven’t gotten the hint by now, take advantage of MATLAB’s
documentation!)
But often it is better to break the computation into a sequence of steps and
assign intermediate results to variables.
>>
>>
>>
>>
>>
shiftx = x - theta;
denom = shiftx * sqrt(2 * pi) * sigma;
temp = (log(shiftx) - zeta) / sigma;
exponent = -1/2 * temp^2;
ans = exp(exponent) / denom
The names of the intermediate variables explain their role in the computation. shiftx is the value of x shifted by theta. It should be no surprise that
exponent is the argument of exp, and denom ends up in the denominator.
Choosing informative names makes the code easier to read and understand
(see the First Theorem of Debugging on page 3).
1.7 Errors
It’s early, but now would be a good time to start making errors. Whenever you
learn a new feature, you should try to make as many errors as possible, as soon
1.7 Errors
9
as possible. When you make deliberate errors, you get to see what the error
messages look like. Later, when you make accidental errors, you will know what
the messages mean.
A common error for beginning programmers is leaving out the * for multiplication.
>> area = pi r^2
area = pi r^2
|
Error: Unexpected MATLAB expression.
The error message indicates that, after seeing the operand pi, MATLAB was
“expecting” to see an operator, like *. Instead, it got a variable name, which is the
“unexpected expression” indicated by the vertical line, | (which is called a “pipe”).
Another common error is to leave out the parentheses around the arguments
of a function. For example, in math notation, it is common to write something
like sin π, but not in MATLAB.
>> sin pi
Undefined function 'sin' for input arguments of type 'char'.
The problem is that when you leave out the parentheses, MATLAB treats the
argument as a string (rather than as an expression). In this case the sin function
generates a reasonable error message, but in other cases the results can be baffling. For example, what do you think is going on here?
>> abs pi
ans = 112
105
There is a reason for this “feature”, but rather than get into that now, let me suggest
that you should always put parentheses around arguments.
This example also demonstrates the Second Theorem of Debugging:
The only thing worse than getting an error message is not getting an
error message.
Beginning programmers hate error messages and do everything they can to make
them go away. Experienced programmers know that error messages are your
friend. They can be hard to understand, and even misleading, but it is worth
making some effort to understand them.
Here’s another common rookie error. If you were translating the following
mathematical expression into MATLAB:
1
p
2 π
t Again, when in doubt, use
parentheses.
10
Chapter 1 Variables and values
You might be tempted to write something like this:
>> 1 / 2 * sqrt(pi)
But that would be wrong. So very wrong. The correct translation would be:
>> 1 / (2 * sqrt(pi))
1.8 Floating-point arithmetic
t The names of these variables are misleading;
floating-point numbers
are sometimes, wrongly,
called “real”.
In mathematics, there are several kinds of numbers: integer, real, rational, irrational, imaginary, complex, etc. MATLAB only has one kind of number, called
floating-point.
You might have noticed that MATLAB expresses values in decimal notation.
So, for example, the rational number 1/3 is represented by the floating-point value
>> 1/3
ans = 0.3333
t You can also change the
format by selecting the
Preferences icon, then selecting Command Window .
There is a drop-down list
next to “Numeric format”
under “Text display”.
which is only approximately correct. It’s not quite as bad as it seems; MATLAB
uses more digits than it shows by default. You can change the format to see the
other digits.
>> format long
>> 1/3
ans = 0.33333333333333
Internally, MATLAB uses the IEEE double-precision floating-point format,
which provides about 15 significant digits of precision (in base 10). Leading and
trailing zeros don’t count as “significant” digits, so MATLAB can represent large
and small numbers with the same precision.
Very large and very small values are displayed in scientific notation.
>> factorial(100)
ans = 9.332621544394410e+157
The e in this notation is not the transcendental number known as e; it is just an
abbreviation for “exponent”. So this means that 100! is approximately 9.33 × 10157 .
The exact solution is a 158-digit integer, but we only know the first 16 digits. You
can enter numbers using the same notation.
>> speed_of_light = 3.0e8
speed_of_light = 300000000
1.8 Floating-point arithmetic
11
Although MATLAB can handle large numbers, there is a limit. The predefined
variables realmax and realmin contain the largest and smallest numbers that
MATLAB can handle.
>> realmax
ans = 1.797693134862316e+308
>> realmin
ans = 2.225073858507201e-308
If the result of a computation is too big, MATLAB “rounds up” to infinity.
>> factorial(170)
ans = 7.257415615307994e+306
>> factorial(171)
ans = Inf
>> realmax*100
ans = Inf
Division by zero and the natural log of zero returns Inf and -Inf, respectively.
>> 1/0
ans = Inf
>> log(0)
ans = -Inf
Allowing Inf to propagate through a computation doesn’t always do what you
expect, but if you are careful with how you use it, Inf can be quite useful.
For operations that are truly undefined, MATLAB returns NaN, which stands
for “not a number”.
>> 0/0
ans = NaN
Lastly, we can change the format back to where we started:
>> format short
>> 1/3
ans = 0.3333
We saw that with very large and small values, MATLAB uses scientific notation. If
t In MATLAB R2009a and earlier, dividing by zero and
the log of zero would produce a warning message
because it is usually considered undefined. A warning
is like an error message, but
the computation is allowed
to continue.
12
Chapter 1 Variables and values
you would like to use scientific notation in general, you can change the format
to do so; you can augment short and long with an e:
>> format short e
>> 1/3
ans = 3.3333e-01
>> format long e
>> 1/3
>> ans = 3.333333333333333e-01
1.9 Comments
Along with the commands that make up a program, it is useful to include comments that provide additional information about the program. The percent
symbol % separates the comments from the code.
>> speed_of_light = 3.0e8
speed_of_light = 300000000
% meters per second
The comment runs from the percent symbol to the end of the line. In this case
it specifies the units of the value. In an ideal world, MATLAB would keep track
of units and propagate them through the computation, but for now that burden
falls on the programmer.
Comments have no effect on the execution of the program. They are there
for human readers. Good comments make programs more readable, but bad
comments are useless or (even worse) misleading.
Avoid comments that are redundant with the code:
>> x = 5
% assign the value 5 to x
Good comments provide additional information that is not in the code, like
units in the example above, or the meaning of a variable:
>> p = 0
>> v = 100
>> a = -9.8
% position from the origin in meters
% velocity in meters / second
% acceleration of gravity in meters / second^2
If you use longer variable names, you might not need explanatory comments,
but there is a trade-off: longer code can become harder to read. Also, if you are
translating from math that uses short variable names, it can be useful to make
your program consistent with your math.
1.10 Examples
13
1.10 Examples
In each chapter I will try to provide ample examples with worked solution. As you
work through the chapter material, you should first attempt to solve the example
problems without looking at the solution. Consult the solutions only if you get
stuck or to check your work. In doing so, the examples will serve as excellent
practice for the exercises at the end of the chapter. For additional help, most of
the examples will link to a screencast on my YouTube Channel of me working
through the solutions and making general comments on the Chapter material.
Often, at the end of the problem statement, I will provide you with a value of
the final numerical solution, ans =. You can use this value to troubleshoot/debug
your work. In later chapters, note that your answer may differ depending on the
numerical method used.
Example 1.1
Write a MATLAB expression that evaluates the following math expression. You can
assume that the variables µ, σ and x already exist.
e
´
³
x−µ 2
− σp2
p
σ 2π
(1.1)
Remember, you can’t use Greek letters in MATLAB; when translating math expressions with Greek letters, it is common to write out the name of the letter (assuming
you know it).
To test your expression, use µ = 2.2, σ = 0.5, and x = 2.
(ans = 0.7365)
Solution: (Link to screen cast.) In my solution, I will break the calculation up into
many small parts to minimize error.
>> x = 2;
>> mu = 2.2;
>> sigma = 0.5;
>> exp_num = x-mu;
>> exp_den = sigma*sqrt(2);
>> exponent = -(exp_num/exp_den)^(2);
>> denominator = sigma*sqrt(2*pi);
>> exp(exponent) / denominator
ans = 0.7365
14
Chapter 1 Variables and values
Example 1.2
Professor Paluch is shopping for furniture. He finds a chair that cost $125 and a
sofa for $200. He decides to purchase 2 chairs and 1 sofa. Assuming he has a 20%
off coupon and sales tax is 8%, what is his total cost?
(ans = 388.80)
Solution: (Link to screen cast.)
>> cost_sofa = 200;
>> cost_chair = 125;
>> subtotal = 2*cost_chair+cost_sofa
subtotal = 450
>> discount = (1-0.2)*subtotal
discount = 360
>> total_w_tax = discount*1.08
total_w_tax = 388.8000
The total is $388.80.
The answer here is fine, but what if we had calculated a dollar amount with three
decimal places? Since we are dealing with money, the final answer should have
two decimal places. While I know you can properly round the solution, we could
also have MATLAB do this for us with the round command. The first argument will
be the number we wish to round, and the second argument will be the number of
decimal places to use. Here is an example:
>> test = 388.8011;
>> round(test,2)
ans = 388.8000
The result is properly round to two decimal places. Notice that MATLAB still
displays four decimal places. This is because of format short.
Example 1.3
Compute: sin2
¡π¢
6
+ cos2
¡π¢
6
(ans = 1)
Solution: (Link to screen cast.)
>> sin(pi/6)^(2) + cos(pi/6)^(2)
ans = 1
1.11 Chemical Engineering Examples
15
The tricky part of this problem is the placement of the exponent. In our math
classes, we typically write sin2 (x) and cos2 (x). However, MATLAB does not recognize this notation. What this means is that the (entire) quantity is squared, and
this is how we write it in MATLAB. In a math class, we prefer the other notation so
that we do not mis-interpret this as only the term in parentheses is squared.
Also note that we have an extra step in our order of operations here. First, MATLAB
evaluates the terms in parentheses, pi/6. Starting from the left, MATLAB then
evaluates the function, sin, and then squares the result. It then evaluates the
second term in the same fashion, and then adds the two together. If you were
unsure, you can add parentheses and get the same result
>> (sin(pi/6))^(2) + (cos(pi/6))^(2)
ans = 1
Example 1.4
Compute the area of a trapezoid with height of 2, and bases of 4 and 7. Remember:
A = 12 h (b 1 + b 2 ).
(ans = 11)
Solution: (Link to screen cast.)
>> h = 2;
>> b1 = 4;
>> b2 = 7;
>> area = 0.5*h*(b1+b2)
area = 11
1.11 Chemical Engineering Examples
Example 1.5
Over limited temperature ranges, the vapor pressure of a pure fluid is commonly
correlated using an Antoine equation of the form
log10 p sat = A −
B
T +C
(1.2)
where A, B , and C are constants. Equation (1.2) can also be solved for T to find the
corresponding saturation temperature at a particular pressure.
T=
B
−C
A − log10 p sat
(1.3)
16
Chapter 1 Variables and values
For ethanol, A = 8.13484, B = 1662.48, and C = 238.131 over the range −114.1 ◦ C <
T < 243.1◦ C, where T is in ◦ C and p sat is in mmHg. When using an Antoine
equation, please always be certain to check units; while units are not provided for
the constants, they are dependent on the units used for T and p sat
a) Calculate the vapor pressure of ethanol at 380 K in units of mmHg, atm, kPa,
and bar. The key to conversion is remembering atmospheric pressure: 1 atm
= 760 mmHg = 101.325 kPa. And 1 bar = 1 × 105 Pa, which is almost equal to 1
atm.
(2069.2 mmHg)
b) Does the vapor pressure of ethanol increase or decrease with increasing T ?
Find out by computing p sat at a few different temperatures.
(increases)
c) Calculate the normal boiling point of ethanol. That is, at what temperature
does it boil at atmospheric pressure?
(ans = 78.289)
Solution: (Link to screen cast.)
Let the fun begin!
a) The provided Antoine equation uses units of ◦ C for temperature and mmHg
for pressure. In this problem we are asked to compute the vapor pressure in
various units at 380 K. Let’s begin by converting the temperature to ◦ C, and
then using the Antoine equation as written to compute the vapor pressure in
mmHg. We can then convert to other units.
For convenience and to make my calculation more readable, I will store the
Antoine coefficients to lowercase a, b, and c, and I will make a note of the units
in the variables I use to store my temperature and pressure.
>> a = 8.13484;
% Antione parameters
>> b = 1662.48;
>> c = 238.131;
>> t_K = 380;
% Temperature
>> t_C = 380-273.15;
>> p_mmHg = 10^(a-b/(t_C+c)) % Pressure
p_mmHg = 2069.2
>> p_atm = p_mmHg*(1/760)
p_atm = 2.7226
>> p_kPa = p_atm*101.325
p_kPa = 275.86
>> p_bar = p_kPa*(1/100)
p_bar = 2.7586
1.11 Chemical Engineering Examples
17
b) In my opinion, one of the best things about computational modeling is the ability to ask “what if” questions. To me, this becomes a very powerful educational
tool. For me, this is better than memorizing facts from a book.
Here, we are asked if the vapor pressure of ethanol increases or decreases
with temperature. From the previous question, we already have our model
set-up. Here we need only change the temperature and re-evaluate p_mmHg
= 10ˆ
(a-b/(t_C+c)). Remember with the up arrow key ( ), we can quickly
recall previous commands, and are able to edit them too.
>> t1_C = 20;
>> t2_C = 40;
>> t3_C = 60;
>> p1_mmHg = 10^(a-b/(t1_C+c))
p1_mmHg = 49.475
>> p2_mmHg = 10^(a-b/(t2_C+c))
p2_mmHg = 143.72
>> p3_mmHg = 10^(a-b/(t3_C+c))
p3_mmHg = 361.83
We find as temperature increases, the vapor pressure increases. Does this make
sense?
c) Last, we are asked to solve for the temperature where the vapor pressure is
equal to 1 atm or 760 mmHg. Remember, using the provided Antoine parameters, the temperature will be in ◦ C and the pressure must be in mmHg. Also,
the equation uses log10 . Remember in MATLAB log is actually the natural log,
ln or loge , and log10 is log10 . The constants are already stored in our session,
so we are all set to go!
>> p_mmHg = 760;
>> t_C = b/(a-log10(p_mmHg)) - c
t_C = 78.289
The normal boiling point of ethanol is 78.289 ◦ C. This is less than the normal
boiling point of water of 100 ◦ C. Ethanol is therefore more volatile than water.
Example 1.6
All around us molecules are moving. It’s a hot summer’s day, and you sit down to
have a tall glass of ice water. How fast are water molecules moving in your glass?
Let’s calculate it!
The kinetic energy of a molecule may be taken to be independent of it’s potential
(or configurational) energy. This allows us to calculate the average kinetic energy
of a molecule as
Ek =
m 2 3
v = kB T
2
2
(1.4)
t In this problem and likely
in many of your classes, you
need to know the values of
several constants and conversion factors. In future
chapters we will build-up
to using scripts and then
functions to solve a problem. Then we can look our
constant up once to write a
function, which we can then
use over and over again.
18
Chapter 1 Variables and values
where m is the mass of a molecule (which is constant), v is the molecular velocity, T is the temperature, and k B is the molecular gas constant (R = k B NAvo ) or
Boltzmann’s constant. We can therefore compute the average speed of a molecule
as
s
q
speed =
v2
=
3k B T
m
(1.5)
So how fast are the molecules moving in your glass of ice water? Recall: R = 8.314
J/(mol·K), NAvo = 6.022 × 1023 mol−1 , and MWwater = 18 amu. You may assume
that the (solid) ice and liquid water are in equilibrium at 0 ◦ C. Report the speed in
both units of m/s and miles/hour. (1 mile = 1609.34 m)
(615.22 m/s)
Note, the average speed is only a function of T and m. For our problem then, since
the (solid) ice and liquid water are in equilibrium at the same T , the ice and liquid
water molecules are moving at exactly the same average speed!
At the same temperature, do heavier or lighter molecules move faster? Do molecules
move faster at higher or lower temperatures?
Solution: (Link to screen cast.)
How cool... no pun intended. I will begin by clearing all of my variables with
the command clear variables since we are beginning a new problem. If you
want to clear your workspace too, try clc. For a problem like this, where there are
various variables with different units, I tend to convert everything to SI and solve.
This way I am confident the units of my final answer will be correct in SI units.
How do we handle amu? An amu can equivalently be written as g/mol. We can
convert to kg/mol (SI units) by dividing by 1000. Then to get mass of a molecule
of water, we can divide by NAvo . Notice then that the NAvo to convert from R to
k B and MWwater to m conveniently cancel out of the expression. This allows us to
re-write the expression as:
s
speed =
3RT
MWwater
where here MWwater is in kg/mol.
Let’s begin by solving for average molecular speed of water at 0 ◦ C (or 273.15 K).
>> clear variables
>> r = 8.314; % gas constant in J/(mol K)
>> mw = 18/1000; % molecular weight of water in kg/mol
>> t = 273.15; % temperature in K
>> speed = sqrt( 3*r*t/mw )
speed = 615.22
Having used SI units, this is the average speed in m/s. Now let’s convert to mph as
requested. We are provided with the conversion factor to go from m to miles. To
go from s to h, remember 60 s equal 1 min, and 60 min equal 1 h.
1.11 Chemical Engineering Examples
19
>> speed_mph = speed*(1/1609.34)*60*60
speed_mph = 1376.2
Wow! It is a good thing a molecule of water does not weigh too much.
At the same temperature, do heavier of lighter molecules move faster? The speed
is inversely proportional to mass, so we expect lighter molecules to move faster.
While we could work this out analytically, let’s use MATLAB and double the molecular weight.
>> mw = 32/1000;
>> speed = sqrt( 3*r*t/mw )
speed = 461.41
The heavier molecules moves slower as expected.
What’s the effect of temperature? Since speed is proportional to temperature, we
expect the speed to increase with increasing temperature. Let’s again use MATLAB,
this time to double the temperature.
>> t = 273.15*2;
>> speed = sqrt( 3*r*t/mw )
speed = 652.54
As the temperature increases, the speed increases. Not that in this last calculation,
the molecular weight was still double that of water.
Example 1.7
When I was an undergraduate student and took my first thermodynamics course
(CPB 314), I recall solving a lot of homework problems that required me to perform
a linear interpolation in the steam tables. Let’s therefore tackle a sample problem
here. Our goal here is understanding and performing a linear interpolation, do not
worry if the thermo part is new to you.
You would like to know the temperature of superheated steam at 40 bar and with a
molar volume of v = 0.1 m3 /kg. In the “Superheated Steam” tables in the back of
your CPB 314 text, you find the following data
T (◦ C)
600
650
v (m3 /kg)
0.0989
0.1049
Shoot, an entry with exactly v = 0.1 m3 /kg is missing. But we do know that it must
lie somewhere between 600 and 650 ◦ C. We can estimate the temperature from
the provided data using a technique called linear interpolation. We will generate a
general solution which you can use in you CPB 314 class, and then apply it here for
the present problem.
Let’s start by setting up a table depicting the general scenario
t Note that the built-in MATLAB function interp1 may
be used to perform a linear
interpolation as we are doing here. However, we need
to discuss vectors first before
we can use it.
20
Chapter 1 Variables and values
X
x1
x2
x3
Y
y1
y2
y3
where X and Y are arbitrary properties (such as T and v), where the following
two points are known: (x 1 , y 1 ) and (x 3 , y 3 ). However, what you need to know
is y 2 for a specified x 2 (i.e. you need the point (x 2 , y 2 ) where x 2 is known). In
linear interpolation, we assume that the three points are co-linear (i.e., they all
lie on a straight line), where (x 2 , y 2 ) lies in between (x 1 , y 1 ) and (x 3 , y 3 ). This is an
assumption, one you should keep in mind.
What is the slope of a straight line? It is a constant. Therefore, the slope from
(x 1 , y 1 ) to (x 2 , y 2 ) is equal to the slope from (x 1 , y 1 ) to (x 3 , y 3 ). Remember from
your math classes that slope = m = rise/run = ∆y/∆x. Now let’s write it out using
math:
y2 − y1 y3 − y1
=
(1.6)
x2 − x1 x3 − x1
All that is left is to solve for y 2 .
y2 − y1 =
y3 − y1
(x 2 − x 1 )
x3 − x1
y3 − y1
(x 2 − x 1 ) + y 1
x3 − x1
You can simplify this expression as much or as little as you would like.
y2 =
(1.7)
(1.8)
Now using our expression, go back and find the value of T where v = 0.1 m3 /kg.
(ans = 609.17)
Solution: (Link to screen cast.)
In this example you can think of v as being your x variable, and T is your y variables. As far as units are concerned in this problem, if we use the same units as in
the table we are interpolating, our result will have the same units. Let’s do it!
>>
>>
>>
>>
>>
>>
t2
v1
v2
v3
t1
t3
t2
=
= 0.0989;
= 0.1;
= 0.1049;
= 600;
= 650;
= (t3-t1)/(v3-v1)*(v2-v1)+t1
609.17
We estimate the temperature to be 609.17 ◦ C.
1.12 MATLAB Online
Before wrapping-up our first introductory chapter, I would like to just briefly
mention the existance of MATLAB Online and MATLAB Drive. At Miami University, our current licence includes access to MATLAB Online, which allows you
1.13 Glossary
to use MATLAB through your web browser, with no downloads or installation
necessary. This gives you the ability to use MATLAB on a tablet, smart phone, or
Chromebook. Or if you find yourself using a computer that you do now own, such
as a loaner from the repair center, on which you can not install MATLAB, then
you can still use MATLAB using MATLAB Online. MATLAB Drive, as the name
suggests, is a cloud based storage system for your MATLAB files.
In order to use MATLAB Online and Drive, you first need to create a MathWorks account. As indicated on the main registration page, to access your organization’s MATLAB license, you must use your work or university email address.
The screen cast Introduction to MATLAB Online from Chapter 1: Variables and
values provides a quick demonstration of the use of MATLAB Online.
1.13 Glossary
interpreter: The program that reads and executes MATLAB code.
command: A line of MATLAB code executed by the interpreter.
prompt: The symbol the interpreter prints to indicate that it is waiting for you to
type a command.
operator: One of the symbols, like * and +, that represent mathematical operations.
operand: A number or variable that appears in an expression along with operators.
expression: A sequence of operands and operators that specifies a mathematical
computation and yields a value.
value: The numerical result of a computation.
evaluate: To compute the value of an expression.
order of operations: The rules that specify which operations in an expression
are performed first.
function: A named computation; for example log10 is the name of a function
that computes logarithms in base 10.
call: To cause a function to execute and compute a result.
function call: A kind of command that executes a function.
argument: An expression that appears in a function call to specify the value the
function operates on.
nested function call: An expression that uses the result from one function call
as an argument for another.
21
22
Chapter 1 Variables and values
variable: A named value.
assignment statement: A command that creates a new variable (if necessary)
and gives it a value.
string: A value that consists of a sequence of characters (as opposed to a number).
floating-point: The kind of number MATLAB works with. All floating-point numbers can be represented with about 16 significant decimal digits (unlike
mathematical integers and reals).
scientific notation: A format for typing and displaying large and small numbers;
e.g. 3.0e8, which represents 3.0 × 108 or 300,000,000.
comment: Part of a program that provides additional information about the
program, but does not affect its execution.
Chapter
2
Scripts
In Chapter 2 we continue to build our foundational knowledge of MATLAB with
the introduction of scripts. By the end of this chapter you will be able to:
• Demonstrate the ability to write and execute MATLAB scripts
• Explain how MATLAB interprets the contents of a script
• Construct a script to solve basic engineering problems
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
Figure 2.1 The New Script button.
You could also find “New Script” in
the drop-down list from the New
button.
t Be sure to have a look at the
2.1 M-files
MATLAB documentation.
So far we have typed all of our programs “at the prompt,” which is fine if you
are not writing more than a few lines. Beyond that, you will want to store your
program in a script and then execute the script.
A script is a file that contains MATLAB code. These files are also called “Mfiles” because they use the extension .m, which is short for MATLAB. You can
create and edit scripts with any text editor or word processor, but the simplest
way is by clicking the New Script button at the far left of the top menu under on
the Home tab. An Editor window appears running a text editor specially designed
for MATLAB.
23
t There are also many other
important reasons to write
scripts, even “short” scripts.
If there is a calculation you
know you will need to perform many times, a script
can save you a great deal
of time. Turning in a script
for a homework solution
or lab report ensures that
others can reproduce your
work. It can also serve as a
lab notebook, documenting
exactly what you did.
24
Chapter 2 Scripts
Figure 2.3 The large Save icon
at the top left of the top menu.
Directly below you will also find
a drop-down menu with options
to “Save”, “Save As”, “Save All” (if
you are working on more than one
script at the same time), and “Save
Copy As”.
Figure 2.2 MATLAB R2018a desktop with an Editor window open.
t If you are using MATLAB
Online, have a look back at
Section 1.12 and watch the
screen cast Introduction
to MATLAB Online from
Chapter 1: Variables and
values which includes an
example of downloading
myscript.
t Just as with functions,
Tab
+
(or
) works
when typing the name of
script files too. If you can
remember just the first few
letters, it will try to auto-fill
the rest. If there is more
than one possibility, a list
of possible commands will
appear. If you get an error,
this is a good check if you
mis-spelled the name or are
in the wrong directory.
+ Tab
t In MATLAB and in general,
a script is nothing more
than a series of commands
one could just as well enter
one after the other in a
command prompt (or here
the Command Window).
Type the following code in the editor
x = 5
and then press the large Save (outdated) floppy disk icon at the top left of the
menu, or select the smaller Save icon in the smaller top right corner of your
MATLAB desktop. You can also use your standard keyboard shortcuts, which for
Windows would be Ctrl + S . Either way, a dialog box appears where you can
choose the file name and the directory where it should go. Change the name to
myscript.m and leave the directory unchanged.
By default, MATLAB will store your script in your current folder. When you run
the script from the Command Window , MATLAB will look in your current folder
for the file, and it will also look in the search path, which is the list of directories
MATLAB searches for scripts. You can see the list of directories in the search path
by clicking on the “Set Path” icon on the Home tab. 1
Go back to the Command Window and type myscript (without the extension)
at the prompt. MATLAB executes your script and displays the result.
>> myscript
x = 5
When you run a script, MATLAB executes the commands in the M-File, one after
another, exactly as if you had typed them at the prompt.
If something goes wrong and MATLAB can’t find your script, you will get an
error message like:
1 If you are curious, information about adding and removing folders to/from the search path
may be found in the MATLAB documentation for “Change Folders on the Search Path.” You can
also add folders to the search path interactively using the “Set Path” icon on the Home tab. You
may find this useful when you become a more advanced MATLAB user.
2.1 M-files
25
>> myscript
Undefined function or variable 'myscript'.
In this case you can either need to save your script again in your current
directory or in a directory that is on the search path, or you need to modify the
search path to include the directory where you keep your scripts. Changing your
current folder is easily accomplished by using the interactive path navigator found
immediately above your windows, but below the menu icons. You can also use
the Current Folder window to interactively navigate within your current folder.
When you are first getting started writing scripts, this discussion of current
folders and paths can be a little confusing. And from my experience teaching this
course, a very common (and frustrating) mistake made by students is saving files
in a directory other than the current folder. If the reading was unclear, have a look
at my “Chapter 2: Current folder and paths” screen cast.
The file name can be anything you want, but you should try to choose something meaningful and memorable. You should be very careful to choose a name
that is not already in use; if you do, you might accidentally replace one of MATLAB’s functions with your own. Finally, the name of the file cannot contain spaces.
If you create a file named my script.m, MATLAB doesn’t complain until you try
to run it:
t We saw previously on
>> my script
Undefined function or variable 'my'.
The problem is that it is looking for a script named my. The problem is even
worse if the first word of the file name is a function that exists. Just for fun, create
a script named abs val.m and run it. Remember, if you would like the effect of a
space in your file name, use an underscore (_) between abs and val.
Before looking at an example, in previous semesters I have found that students
like to use the “Run” button to execute their scripts. Simply clicking the run button
can also be used to run functions without any inputs. It is equivalent to typing
the name of the M-file in the Command Window . The Run button is located on the
Editor tab. In general, simply clicking the Run button will work for scripts and
functions with no inputs. I tend to avoid using the Run button. This way when
we deal later with functions, I am certain to assign the output to a variable other
than “ans”, and I can make sure I pass any variables that need to be passed. But
more on that later. For now, if you are curious, have a look at my Chapter 2: Run
Button screen cast.
page 5 that when we typed
SIN, MATLAB suggested
it was a possible typo and
asked if we meant sin. If
a script (or function) with
a similar name is present
in your path, MATLAB may
make a suggestion.
Figure 2.4 The large “Run” button
on the Home tab. Simply clicking
the Run button is equivalent to
typing the name of the M-file in
the Command Window .
26
Chapter 2 Scripts
Example 2.1
t Here and in the future, I
The Fibonacci sequence, denoted F , is described by the equations F 1 = 1, F 2 = 1,
and for i ≥ 3, F i = F i −1 + F i −2 . The elements of this sequence occur naturally in
many plants, particularly those with petals or scales arranged in the form of a
logarithmic spiral.
will provide you with a copy
of the example script file
that I provide in the text.
The name of the file may be
found following the Listing
heading. I will also link to
an electronic copy of the file
that you can download from
my Google Drive.
The following expression computes the n th Fibonacci number:
1
Fn = p
5
"Ã
p !n Ã
p !n #
1+ 5
1− 5
−
2
2
(2.1)
Translate this expression into MATLAB and store your code in a file named fibonacci1.
At the prompt, set the value of n to 10 and then run your script. The last line of
your script should assign the value of F n to ans.
(ans = 55.0000)
t Note that on the
Home tab,
under Preferences MATLAB
Editor/Debugger , you will
find options available for
“Automatic file changes”. If
you check the box “Save
changes upon clicking away
from a file”, your script (or
function) being edited will
automatically be saved if
you click away from the file.
This feature was activated
by default when I started
MATLAB R2015a and R2018a
for the very first time.
Solution: (Link to screen cast with accompanying M-file.) Let’s start by writing the
script. I will provide you with two examples. First, since the equation is not very
long, I will write everything on a single line. Second, I will break up the calculation
into smaller, more manageable parts. The later approach is often preferred to
minimize the occurrence of errors due to syntax and also to make the code more
readable and easier to debug. Also, note that I use ans = here only to make the
script more readable; it makes it clear what the intended answer is. If we were to
omit this, ans = would still be printed to screen when we run the script; remember
ans is the default variable MATLAB uses to store a result to when an alternative
name is not provided.
Listing 2.1 fibonacci1_long.m
1 ans = 1/sqrt(5) * ( ((1+sqrt(5))/2)^n - ((1-sqrt(5))/2)^n )
Listing 2.2 fibonacci1.m
1
2
3
4
5
s5 = sqrt(5);
t1 = (1 + s5) / 2;
t2 = (1 - s5) / 2;
dfib = t1^n - t2^n;
ans = dfib / s5
Save the script to your current path. Then to compute the n th Fibonacci number:
>> n = 10;
>> fibonacci1_long
ans = 55.0000
>> fibonacci1
ans = 55.0000
2.2 Why scripts?
27
2.2 Why scripts?
The most common reasons to use scripts are:
• When you are writing more than a couple of lines of code, it might take
a few tries to get everything right. Putting your code in a script makes it
easier to edit than typing it at the prompt.
t Remember, you can change
the arrangement of your
windows using the Layout
icon in the top menu bar.
On the other hand, it can be a pain to switch back and forth between the
Command Window and the Editor . Try to arrange your windows so you can
see the Editor and the Command Window at the same time, and use the
mouse to switch between them.
• If you choose good names for your scripts, you will be able to remember
which script does what, and you might be able to reuse a script from one
project to the next. Similarly, don’t “reinvent the wheel” every time you
solve a problem; if you have script for a similar problem, use it as your
starting point and modify it as necessary. We will revisit this idea later in
this chapter on page 35 as the first step of incremental development.
• If you run a script repeatedly, it is faster to type the name of the script than
to retype the code!
Unfortunately, the great power of scripts comes with great responsibility, which is
that you have to make sure that the code you are running is the code you think
you are running.
First, whenever you edit your script, you have to save it before you run it. If
you forget to save it, you will be running the old version. Also, whenever you start
a new script, start with something simple, like x=5, that produces a visible effect.
Then run your script and confirm that you get what you expect. MATLAB comes
with a lot of predefined functions. It is easy to write a script that has the same
name as a MATLAB function, and if you are not careful, you might find yourself
running the MATLAB function instead of your script. This will also make sure you
are saving in the search path. Either way, if the code you are running is not the
code you are looking at, you will find debugging a frustrating exercise! And that
brings us to the Third Theorem of Debugging:
t So start by making sure your
You must always be 100% sure that the code you are running is the
code you think you are running.
2.3 The workspace
The variables you create are stored in the workspace, which is a set of variables
and their values. The who command prints the names of the variables in the
workspace.
script actually runs. Then
if problems arise, you can
focus on the the code itself.
28
Chapter 2 Scripts
>>
>>
>>
>>
x=5;
y=7;
z=9;
who
Your variables are:
x y z
The clear command removes variables.
>> clear y
>> who
Figure 2.5 If you prefer a graphical
environment, don’t forget about
the Workspace window. To clear
a variable, you can highlight it by
clicking, then hit the Delete key on
your keyboard, or right-click with
your mouse then select “Delete”.
If you right-click, you will also
find other useful commands that
should be self-explanatory.
Your variables are:
x z
You can clear more than one variable at a time by listing the variables sequentially.
>> y = 7;
>> clear x y
>> who
Your variables are:
z
If you wish to clear all of the variables, you could list all of the variables sequentially, or for this specific case you can use the command clear variables.
>>
>>
>>
>>
x=5;
y=7;
clear variables
who
To display the value of a variable, you can use the disp function.
>> z=9;
>> disp(z)
9
But it’s easier to just type the variable name.
>> z
z = 9
2.4 More errors
(Strictly speaking, the name of a variable is an expression, so evaluating it should
assign a value to ans, but MATLAB seems to handle this as a special case.)
On the topic, if you wish to clear the command window, use clc.
2.4 More errors
Again, when you try something new, you should make a few mistakes on purpose
so you’ll recognize them later.
The most common error with scripts is to run a script without creating the
necessary variables. For example, fibonacci1 requires you to assign a value to n.
If you don’t:
>> fibonacci1
Undefined function or variable 'n'.
Error in fibonacci1 (line 9)
dfib = t1^n - t2^n;
The details of this message might be different for you, depending on what’s in
your script. But the general idea is that n is undefined. Notice that MATLAB tells
you what line of your program the error is in, and displays the line.
This information can be helpful, but beware! MATLAB is telling you where
the error was discovered, not where the error is. The reported line is where n is
first used. And since it is not defined, MATLAB stops at this point. In this case,
the error is not in the script at all; it is, in a sense, in the workspace.
Which brings us to the Fourth Theorem of Debugging:
Error messages tell you where the problem was discovered, not
where it was caused.
The object of the game is to find the cause and fix it—not just to make the error
message go away.
2.5 Pre- and post-conditions
Every script should contain a comment that explains what it does, and what the
requirements are for the workspace. For example, I might put something like this
at the beginning of fibonacci1 (Listing 2.2):
29
30
Chapter 2 Scripts
fibonacci1.m
1
2
3
4
5
6
%
% Computing the nth Fibonacci number using equation 2.1
% Precondition: you must assign a value to n before
%
running this script.
% Postcondition: the result is stored in ans.
%
A precondition is something that must be true, when the script starts, in order
for it to work correctly. A postcondition is something that will be true when the
script completes.
If there is a comment at the beginning of a script, MATLAB assumes it is
the documentation for the script, so if you type help fibonacci1, you get the
contents of the comment (without the percent signs).
Figure 2.6 The Help window that
opens following the command doc
fibonacci1.
>> help fibonacci1
Computing the nth Fibonacci number using equation 2.1
Precondition: you must assign a value to n before
running this script.
Postcondition: the result is stored in ans.
That way, scripts that you write behave just like predefined scripts. You can even
use the doc command to see your comment in the Help window.
2.6 Assignment and equality
In mathematics the equals sign means that the two sides of the equation have
the same value. In MATLAB an assignment statement looks like a mathematical
equality, but it’s not.
One difference is that the sides of an assignment statement are not interchangeable. The right side can be any legal expression, but the left side has to be
a variable, which is called the target of the assignment. So this is legal:
>> y = 1;
>> x = y+1
x = 2
But this is not:
>> y+1 = x
y+1 = x
|
Error: The expression to the left of the equals sign is not a
valid target for an assignment.
2.6 Assignment and equality
In this case the error message is pretty helpful, as long as you know what a “target”
is.
Another difference is that an assignment statement is only temporary, in the
following sense. When you assign x = y+1, you get the current value of y. If y
changes later, x does not get updated.
A third difference is that a mathematical equality is a statement that may or
may not be true. For example, y = y + 1 is a statement that happens to be false
for all real values of y. In MATLAB, y = y+1 is a sensible and useful assignment
statement. It reads the current value of y, adds one, and replaces the old value
with the new value.
>> y = 1;
>> y = y+1
y = 2
When you read MATLAB code, you might find it helpful to pronounce the
equals sign “gets” rather than “equals.” So x = y+1 is pronounced “x gets the
value of y plus one.”
To test your understanding of assignment statements, try this exercise:
31
32
Chapter 2 Scripts
Example 2.2
Write a few lines of code that swap the values of x and y. Put your code in a script
called swap and test it.
Solution: (Link to screen cast with accompanying M-file.) Let’s start by writing the
script.
Listing 2.3 swap.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
%
% Swap the current value of x with the current value of y
% Precondition: you must assign a value to x and y before running
%
this script.
% Postcondition: the initial value of y is stored as the new value
%
of x.
%
the initial value of x is stored as the new value
%
of y.
%
% Storing a copy of the intial value of y
y0 = y;
% Now re-assigning
y = x
x = y0
% And now clearing (or deleting) the new variable y0 that we created
clear y0;
Next define initial values to y and x then run our script.
>> y = 2;
>> x = 4;
>> swap
y = 4
x = 2
2.6 Assignment and equality
33
Example 2.3
Imagine that you are the owner of a car rental company with two locations, Albany
and Boston. Some of your customers do “one-way rentals,” picking up a car in
Albany and returning it in Boston, or the other way around. Over time, you have
observed that each week 5% of the cars in Albany are dropped off in Boston, and
3% of the cars in Boston get dropped off in Albany. At the beginning of the year,
there are 150 cars at each location.
Write a script called car_update that updates the number of cars in each location from one week to the next. The precondition is that the variables a and b
contain the number of cars in each location at the beginning of the week. The
postcondition is that a and b have been modified to reflect the number of cars that
moved.
To test your program, initialize a and b at the prompt and then execute the script.
The script should display the updated values of a and b, but not any intermediate
variables.
Note: cars are countable things, so a and b should always be integer values. You
might want to use the round function to compute the number of cars that move
during each week.
If you execute your script repeatedly, you can simulate the passage of time from
week to week. What do you think will happen to the number of cars? Will all the
cars end up in one place? Will the number of cars reach an equilibrium, or will it
oscillate from week to week?
In the next chapter we will see how to “automatically” execute your script repeatedly, and how to plot the values of a and b versus time.
(After 1 week: a = 147 and b = 153)
Solution: (Link to screen cast with accompanying M-file.)
Listing 2.4 car_update.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
%
%
%
%
%
%
%
%
%
Update the number of cars in Albany and Boston from one weeek to the
next.
Precondition: you must assign the number of cars in Albany and
Boston at the start of the week to a and b,
respectively.
Postcondition: a and b have been modified to reflect the number
of cars that moved during the week.
% First we calculate the net number of cars gained/lost in Boston
%
5% or the cars in Albany (a) are dropped off in Boston (b)
%
3% of the cars in Boston (b) are dropped off in Albany (a)
atob = round(0.05*a) - round(0.03*b);
% Now updating the number of cars in each location
% (conservation of cars)
b = b + atob
a = a - atob
34
Chapter 2 Scripts
Let’s simulation the passage of a few weeks here. On your own you can try more.
>> b = 150;
>> a = 150;
>> car_update % Week 1
b = 153
a = 147
>> car_update % Week 2
b = 155
a = 145
>> car_update % Week 3
b = 157
a = 143
2.7 Updating variables
In Example 2.3, you might have been tempted to write something like:
a = a - 0.05*a + 0.03*b
b = b + 0.05*a - 0.03*b
But that would be wrong, so very wrong. Why? The problem is that the first line
changes the value of a, so when the second line runs, it gets the old value of b
and the new value of a. As a result, the change in a is not always the same as the
change in b, which violates the principle of Conversation of Cars!
One solution is to use temporary variables anew and bnew:
anew = a - 0.05*a + 0.03*b
bnew = b + 0.05*a - 0.03*b
a = anew
b = bnew
This has the effect of updating the variables “simultaneously;” that is, it reads
both old values before writing either new value.
The following is an alternative solution that has the added advantage of simplifying the computation:
atob = 0.05*a - 0.03*b
a = a - atob
b = b + atob
2.8 Incremental development
It is easy to look at this code and confirm that it obeys “Conversation of Cars”.
Even if the value of atob is wrong, at least the total number of cars is right. And
that brings us to the Sixth Theorem of Debugging:
The best way to avoid a bug is to make it impossible.
In this case, removing redundancy also eliminates the opportunity for a bug.
2.8 Incremental development
When you start writing scripts that are more than a few lines, you might find
yourself spending more and more time debugging. The more code you write
before you start debugging, the harder it is to find the problem.
Incremental development is a way of programming that tries to minimize
the pain of debugging. The fundamental steps are:
1. Always start with a working program. If you have an example from a book
or a program you wrote that is similar to what you are working on, start
with that. Otherwise, start with something you know is correct, like x=5.
Run the program and confirm that you are running the program you think
you are running.
This step is important, because in most environments there are lots of little
things that can trip you up when you start a new project. Get them out of
the way so you can focus on programming.
2. Make one small, testable change at a time. A “testable” change is one that
displays something on the screen (or has some other effect) that you can
check. Ideally, you should know what the correct answer is, or be able to
check it by performing another computation.
3. Run the program and see if the change worked. If so, go back to Step 2. If
not, you will have to do some debugging, but if the change you made was
small, it shouldn’t take long to find the problem.
When this process works, you will find that your changes usually work the first
time, or the problem is obvious. That’s a good thing, and it brings us to the Fifth
Theorem of Debugging:
The best kind of debugging is the kind you don’t have to do.
In practice, there are two problems with incremental development:
• Sometimes you have to write extra code to generate visible output that you
can check. This extra code is called scaffolding because you use it to build
the program and then remove it when you are done. But the time you save
on debugging is almost always worth the time you spend on scaffolding.
35
36
Chapter 2 Scripts
• When you are getting started, it is usually not obvious how to choose the
steps that get from x=5 to the program you are trying to write.
If you find yourself writing more than a few lines of code before you start
testing, and you are spending a lot of time debugging, you should try incremental
development.
2.9 Unit testing
In large software projects, unit testing is the process of testing software components in isolation before putting them together.
The programs we have seen so far are not big enough to need unit testing, but
the same principle applies when you are working with a new function or a new
language feature for the first time. You should test it in isolation before you put it
into your program.
For example, suppose you know that x is the sine of some angle and you want
to find the angle. You find the MATLAB function asin, and you are pretty sure it
computes the inverse sine function. Pretty sure is not good enough; you want to
be very sure. Since we know sin 0 = 0, we could try
>> asin(0)
ans = 0
which is correct. Also, we know that the sine of 90 degrees is 1, so if we try asin(1),
we expect the answer to be 90, right?
>> asin(1)
ans = 1.5708
t Note, we could alternatively
use asind for degrees.
Oops! We forgot that the trig functions in MATLAB work in radians, not degrees.
So the correct answer is π/2, which we can confirm by dividing through by pi:
>> asin(1) / pi
ans = 0.5000
With this kind of unit testing, you are not really checking for errors in MATLAB,
you are checking your understanding. If you make an error because you are
confused about how MATLAB works, it might take a long time to find, because
when you look at the code, it looks right.
Which brings us to the Seventh Theorem of Debugging:
The worst bugs aren’t in your code; they are in your head.
This example is a strategy I use a lot; working interactively on the command
line to test my MATLAB understanding before implementing in my program.
2.10 Kinds of error
While this is one example of unit testing, you might also think of a large programming project as a block flow diagram encountered in your mass and energy
balance course; work on each unit operation (or block) separately, one at a time,
then combine them to model the entire process.
2.10 Kinds of error
There are four kinds of error we will encounter:
Syntax error: You have written a MATLAB command that cannot execute because it violates one of the rules of syntax. For example, you can’t have two
operands in a row without an operator, so pi r^2 contains a syntax error.
When MATLAB finds a syntax error, it prints an error message and stops
running your program.
Runtime error: Your program starts running, but something goes wrong along
the way. For example, if you try to access a variable that doesn’t exist, that’s
a runtime error. When MATLAB detects the problem, it prints an error
message and stops.
Logical error: Your program runs without generating any error messages, but it
doesn’t do the right thing. The problem in Section 2.7, where we changed
the value of a before reading the old value, is a logical error.
Numerical error: Most computations in MATLAB are only approximately right.
Most of the time the errors are small enough that we don’t care, but in some
cases the roundoff errors are a problem.
Syntax errors are usually the easiest. Sometimes the error messages are confusing, but MATLAB can usually tell you where the error is, at least roughly.
Run time errors are harder because, as I mentioned before, MATLAB can tell
you where it detected the problem, but not what caused it.
Logical errors are hard because MATLAB can’t help at all. Only you know what
the program is supposed to do, so only you can check it. From MATLAB’s point of
view, there’s nothing wrong with the program; the bug is in your head!
Numerical errors can be tricky because it’s not clear whether the problem
is your fault. For most simple computations, MATLAB produces the floatingpoint value that is closest to the exact solution, which means that the first 15
significant digits should be correct. But some computations are ill-conditioned,
which means that even if your program is correct, the roundoff errors accumulate
and the number of correct digits can be smaller. Sometimes MATLAB can warn
you that this is happening, but not always! Precision (the number of digits in the
answer) does not imply accuracy (the number of digits that are right). Numerical
errors is a topic that will come up repeatedly in this course.
37
38
Chapter 2 Scripts
2.11 Absolute and relative error
There are two ways of thinking about numerical errors, called absolute and relative.
An absolute error is just the difference between the correct value and the
approximation. We usually write the magnitude of the error, ignoring its sign,
because it doesn’t matter whether the approximation is too high or too low.
p
For example, we might want to estimate 9! using the formula 18π(9/e)9 . The
exact answer is 9 · 8 · 7 · 6 · 5 · 4 · 3 · 2 · 1 = 362, 880. The approximation is 359, 536.87.
The absolute error is 3,343.13.
At first glance, that sounds like a lot—we’re off by three thousand—but it is
worth taking into account the size of the thing we are estimating. For example,
$3,000 matters a lot if we are talking about my annual salary, but not at all if we
are talking about the national debt.
A natural way to handle this problem is to use relative error, which is the error
expressed as a fraction (or percentage) of the exact value. In this case, we would
divide the error by 362,880, yielding 0.00921, which is just less than 1%. For many
purposes, being off by 1% is good enough.
2.12 CPB Examples
Example 2.4
Let’s revisit Example 1.5 on example 1.5. Write two scripts to calculate the vapor
pressure (eq. (1.2)) and saturation temperature (eq. (1.3)) of a pure fluid using
Antoine’s equation. Since you anticipate using Antoine’s equation to model a wide
range of fluids, as a precondition require that the variables a, b and c contain your
Antoine parameters, and that variable tsat or psat contain your temperature or
pressure, depending on if you would like to compute p sat or T .
Remember that the Antoine parameters are dependent on the units of p sat and T .
I would therefore be sure to include this information as a comment at the top of
your script so that a user can find this information using help. Tell the user what
the precondition units of tsat and psat should be. They don’t necessarily need
to agree with the Antoine equation; you can perform the unit conversion at the
beginning of the script file. Use units that you believe will be most useful. Same is
true for the postcondition value of psat or tsat, which ever you are calculating.
You can perform the Antoine calculations in ◦ C and mmHg, then convert to your
preferred units. Again, just include this information as a comment at the start of
your script file. (For problem a, you might consider outputting the pressure in a
range of units.)
Next, let’s use our scripts to solve again Example 1.5 a), b) and c). For ethanol,
A = 8.13484, B = 1662.48, and C = 238.131 over the range −114.1 ◦ C < T < 243.1◦ C,
where T is in ◦ C and p sat is in mmHg.
a) Calculate the vapor pressure of ethanol at 380 K in units of mmHg, atm, kPa,
and bar. The key to conversion is remembering atmospheric pressure: 1 atm
2.12 CPB Examples
39
= 760 mmHg = 101.325 kPa. And 1 bar = 1 × 105 Pa, which is almost equal to 1
atm. (Note, be sure to have your script perform the temperature conversion.)
(2069.2 mmHg)
b) Does the vapor pressure of ethanol increase or decrease with increasing T ?
Find out by computing p sat at a few different temperatures.
(increases)
c) Calculate the normal boiling point of ethanol. That is, at what temperature
does it boil at atmospheric pressure?
(ans = 78.289)
Solution: (Link to screen cast with accompanying M-files.)
For this problem, our solutions are identical to the Solution of Example 1.5. The
only difference here is that we are asked to write scripts rather than perform all of
the calculations from the command line.
a) Let’s start by writing the script. I will keep the script general and ask the user to
provide Antoine parameters. This way we can apply the script to other systems.
You could just as well include the parameters in the script, both are correct.
Listing 2.5 Example_2_4a.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
%
%
%
%
%
%
%
%
%
%
%
Calculating the vapor pressure of ethanol using Antione's equation
Dr. Paluch, Example 2.4a
Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
oC) stored to variables a, b, and c, and the temperature
in K stored to variable t_K.
Post-condition: Psat in units of mmHg (p_mmHg), atm (p_atm),
kPa (p_kPa), and bar (p_bar), and temperature in
degrees C (t_C)
% First, let's convert T from oC to K
t_C = t_K-273.15;
% Calcutating log_p, with p in mmHg
log_p_mmHg = a-b/(t_C+c);
% p in mmHg
p_mmHg = 10^log_p_mmHg
% p in atm
p_atm = p_mmHg/760
% p in kPa
p_kPa = p_atm*101.325
% p in bar
p_bar = p_kPa/100
>>
>>
>>
>>
>>
t_K = 380;
a = 8.13484;
b = 1662.48;
c = 238.131;
Example_2_4a
40
Chapter 2 Scripts
p_mmHg = 2069.2
p_atm = 2.7226
p_kPa = 275.86
p_bar = 2.7586
This is in perfect agreement with the Solution of Example 1.5.
b) To see if the vapor pressure increases with increasing temperature, we need
just specify a new temperature then re-run the script. Our Antoine parameters
were already defined in the last problem. First let’s decreases the temperature
and then let’s increase it.
>> t_K = 380-80;
>> Example_2_4a
p_mmHg = 72.591
p_atm = 0.095514
p_kPa = 9.6780
p_bar = 0.096780
>> t_K = 380 + 40;
>> Example_2_4a
p_mmHg = 6553.9
p_atm = 8.6235
p_kPa = 873.78
p_bar = 8.7378
Once again, we find that as the temperature increases, the vapor pressure
increases.
c) Similar to part (a), let’s start by writing the script, where again we will keep the
script as general as possible to facilitate extension to other fluids.
Listing 2.6 Example_2_4c.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
% At a given pressure, calculate the correspnoding boiling point
% (or saturation temperature) using Antione's equation.
%
% Dr. Paluch, Example 2.4c
%
% Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
%
oC) stored to variables a, b, and c, and the pressure
%
in units of atm stored to p_atm.
% Post-condition: The saturation temperature in units of oC (t_C)
%
% Convert the inputed pressure from atm to mmHg
p_mmHg = p_atm*760;
% Calculating the saturation temperature
t_C = b/(a-log10(p_mmHg))-c
The Antoine parameters were already defined in our workspace in part (a), so
here we need just specify the pressure for which we desire to know the saturation temperature, in this case 1 atm. Take note of the units used; I use units of
2.12 CPB Examples
atm for pressure for the input pressure, and then convert to mmHg in the script.
>> p_atm = 1;
>> Example_2_4c
t_C = 78.289
In this exercise, the instructions stated: “Since you anticipate using Antoine’s equation to model a wide range of fluids, as a precondition require that the variables a,
b and c contain your Antoine parameters, and that variable tsat or psat contain
your temperature or pressure.” However, know that this is not always the best
practice. I tell you to do so here as an academic exercise and to become familiar
the interactions of scripts with your workspace. But if I think of a submission for
homework, lab or project, I would create a script where at least the Antoine parameters are hard coded, and I might update the name of the file to contain the name
of the species whose Antoine parameters are provided. Then if someone wanted
to look at another fluid, they could easily save a copy of the file and update the
parameters, assuming you have clear documentation. And if for your homework
you were looking at only a specific temperature or pressure, I would hard code that
too.
Example 2.5
Revisit Example 1.6 on example 1.6 and write a script to calculate the average speed
of a molecule at a given temperature using eq. (1.5). As a precondition, require that
the molecular weight of the molecule of interest and the temperature are stored
to variables. Please be sure to communicate the expected variable names and
their units at the start of your script as a comment, so the user can see them with
the help command. Please also communicate the units of the (postcondition)
computed speed.
How fast are the molecules moving in a glass of ice water? The molecular weight of
water is MWwater = 18 amu, and you may assume that the temperature is 0 ◦ C.
What if your glass also contained liquid ethanol molecules at the same temperature.
Would the ethanol molecules or water molecules be moving faster? By how much?
The molecular weight of ethanol is MWethanol = 46 amu.
Solution: (Link to screen cast and accompanying M-file.)
As in the last Example, our solutions are identical to the Solution of Example 1.6.
The only difference here is that we are asked to write scripts rather than perform
all of the calculations from the command line. I will start by writing the script.
The script will be general and ask the user to specify temperature in ◦ C and the
molecular weight in amu. This way we can apply the script to other systems. You
could just as well include the parameters in the script and then update them for
each system, which would be just as easy and would actually be a better way
of documenting your solution... either is correct. Note that as compared to the
Solution of Example 1.6, I have not simplified the expression. This will be a good
check of our previous solution.
41
42
Chapter 2 Scripts
Listing 2.7 Example_2_5.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
% At a given temperature, calculate the average speed of a molecule
% using the Maxwell-Boltzmann distribution.
%
% Dr. Paluch, Example 2.5
%
% Pre-condition: The temperature in oC stored to t_C, and the molecular
%
weight of the molecule of interest in amu stored to mw.
% Post-condition: The average speed in units of m/s stored to speed_mps
%
% Convert the temperature from oC to K
t_K = t_C+273.15;
% Avogadro's number
navo = 6.022e23;
% Gas constant in J/mol K
r = 8.314;
% Boltzmann's constant, kB, in J/K
kB = r/navo;
% Calculating the mass of a molecule in kg
mass_kg = mw/navo/1000;
% Calculating the speed in m/s
speed_mps = sqrt(3*kB*t_K/mass_kg)
Start by applying to water at 0 ◦ C.
>> mw = 18;
>> t_C = 0;
>> Example_2_5
speed_mps = 615.22
This is in perfect agreement with the Solution of Example 1.6.
Next, we are asked to compare the speed to the speed of ethanol molecules at the
same temperature. I will start by saving our previous result for water to a new
variable. Then I will re-run the script with the molecular weight of ethanol. Finally,
we will be able to compare to results to see how much faster water is.
>> speed_water = speed_mps;
>> mw = 46;
>> Example_2_5
speed_mps = 384.85
>> water_to_ethanol = speed_water/speed_mps
water_to_ethanol = 1.5986
We find that the average speed of water is greater than water, as expected since
water has the smaller mass. At 0 ◦ C, we find that the average speed of water is
1.5986 times larger than ethanol.
2.12 CPB Examples
43
Example 2.6
Many of you are currently taking engineering thermodynamics or will take it next
semester. Let’s therefore revisit Example 1.7 on example 1.7 and write a script
to perform linear interpolation. Hopefully you will find the resulting script to be
useful in your class. In your script, please be sure to communicate the required
precondition input. To be absolutely clear, I might suggest including a table like
that used to derive the general expression in Example 1.7.
Test your code on Example 1.7: You would like to know the temperature of superheated steam at 40 bar and with a molar volume of v = 0.1 m3 /kg. In the
“Superheated Steam” tables in the back of your engineering thermodynamics text,
you find the following data
T (◦ C)
600
650
v (m3 /kg)
0.0989
0.1049
An entry with exactly v = 0.1 m3 /kg is missing. However, we know that it must lie
somewhere between 600 and 650 ◦ C. Estimate the temperature from the provided
data using linear interpolation.
Solution: (Link to screen cast and accompanying M-file.)
We are asked to perform liner interpolation. It will be kept general, using x and y
as variables. We are then asked to use it to interpolate in the superheated steam
tables, exactly as in the Solution to Example 1.7.
Let’s start by writing the script:
Listing 2.8 Example_2_6.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
Perform linear interpolation on data of the form
X | Y
----x1 | y1
x2 | y2
x3 | y3
were we know all values of y2, which we wish to estimate. Note that
x3>x2>x1.
Dr. Paluch, Example 2.6
Pre-condition: x1, x2, x3, y1 and y3
Post-condition: y2
y2 = (y3-y1)/(x3-x1)*(x2-x1)+y1
For our problem, v corresponds to x, and T corresponds to y.
44
Chapter 2 Scripts
>>
>>
>>
>>
>>
>>
y2
x1 = 0.0989;
x2 = 0.1;
x3 = 0.1049;
y1 = 600;
y3 = 650;
Example_2_6
= 609.17
2.13 Glossary
M-file: A file that contains a MATLAB program.
script: An M-file that contains a sequence of MATLAB commands.
search path: The list of directories where MATLAB looks for M-files.
workspace: A set of variables and their values.
precondition: Something that must be true when the script starts, in order for it
to work correctly.
postcondition: Something that will be true when the script completes.
target: The variable on the left side of an assignment statement.
incremental development: A way of programming by making a series of small,
testable changes.
scaffolding: Code you write to help you program or debug, but which is not part
of the finished program.
unit testing: A process of testing software by testing each component in isolation.
absolute error: The difference between an approximation and an exact answer.
relative error: The difference between an approximation and an exact answer,
expressed as a fraction or percentage of the exact answer.
2.14 Exercises
45
2.14 Exercises
Exercise 2.1 “Yaws’ Critical Property Data for Chemical Engineers and Chemists”2 recommends the following equation to compute the surface tension of saturated organic
compounds (i.e., organic compounds at vapor/liquid equilibrium):
σ = A + BT +C T 2 + DT 3 + E T 4 + F T 5
(2.2)
where σ is the surface tension in units of N/m, T is the temperature in K, and A, B , C ,
D, E , and F are regressed coefficients. The surface tension is the force per unit length
needed to separate the molecules at the vapor/liquid interface. It can equivalently be
related to the vapor/liquid free energy barrier per unit area:
σ=
1F
2A
(2.3)
At the critical point, the two phases become one, and the vapor/liquid free energy barrier and hence surface tension go to zero. This is a famous property that Guggenheim
exploited to estimate the critical temperature of compounds. He would measure the
surface tension at a range of accessible temperatures, and then extrapolate to where the
surface tension went to zero. Cool!
compound
propane
ethanol
acetone
A × 102
4.9138
0.8673
5.7452
B × 104
−0.9349
6.6647
0.4625
C × 106
−0.7665
−4.7511
−1.4587
D × 108
0.4318
1.3783
0.5045
E × 1011
−1.1075
−1.9592
−0.8216
F × 1014
1.1416
1.1142
0.5357
Tmin [K ]
86
250
180
Tmax [K ]
369.82
513.9
508.10
a) Write a script to compute surface tension using the expression suggested by Yaws’.
b) Using your script, compute the surface tension of propane, ethanol, and acetone at
a range of temperatures. Does surface tension increase or decrease with increasing
temperature? Does the surface tension appear to approach zero at the critical point?
(Tmax corresponds to the critical temperature.)
c) Propane is a linear alkane consisting of three methyl groups. We can picture ethanol as
propane where one of the terminal methyl groups has been replaced with a hydroxyl
group. Comparing the surface tension of propane and ethanol can therefore give us an
idea of the difference in intermolecular interaction strength of a methyl and hydroxyl
group. Compute the surface tension of propane and ethanol at 300 K, and compare. In
which system are the intermolecular interactions strongest? Next compare to acetone.
What is the effect of the carbonyl group.
2 Yaws, Carl L. (2012; 2013; 2014).
Yaws’ Critical Property Data for Chemical Engineers
and Chemists. Knovel. Retrieved from https://app.knovel.com/hotlink/toc/id:kpYCPDCECD/
yaws-critical-property/yaws-critical-property
46
Chapter 2 Scripts
Exercise 2.2 “Yaws’ Critical Property Data for Chemical Engineers and Chemists” recommends use of a modified Watson equation to calculate the enthalpy of vaporization as a
function of temperature:
¶
µ
T n
∆H vap = A 1 −
B
(2.4)
where A, B and n are regressed coefficients, T is the temperature in K, and ∆H vap is
the enthalpy of vaporization in units of kJ/mol. ∆H vap corresponds to the change in
enthalpy in going from a liquid to vapor, where the two phases are at equilibrium (same
temperature and pressure). Like surface tension, ∆H vap can be related to intermolecular
interactions. If we assume the vapor phase is an ideal gas (a very good approximation at
atmospheric pressure), then:
∆U coh = ∆H vap − RT
(2.5)
coh
where ∆U
is the cohesive energy, or the change in internal energy upon vaporizing a
fluid, and R is the molar gas constant. In going from a liquid to an ideal gas, where the
molecules do not interact, ∆U coh is a direct measure of the intermolecular interactions
in the liquid phase. In comparing two fluids at the same temperature, the RT term is
constant and we can just as well compare values of ∆H vap .
compound
ethanol
ethane
propane
n-butane
n-pentane
n-hexane
n-heptane
A
60.8036
21.3420
26.8896
33.0198
39.8543
45.61
49.73
B
516.25
305.42
369.82
425.18
469.65
507.43
540.26
n
0.380
0.403
0.365
0.377
0.398
0.401
0.386
Tmin [K ]
300.00
90.35
85.44
134.86
143.42
177.84
182.56
Tmax [K ]
516.25
305.42
369.82
425.18
469.65
507.43
540.26
a) Write a script to compute ∆H vap .
b) Compute ∆H vap for propane and ethanol at a range of temperatures. Does ∆H vap
increase or decrease with increasing temperature? (Note that ∆H vap also goes to zero
at the critical point.)
c) Compute ∆H vap of propane and ethanol at 300 K. In which system are intermolecular
interactions strongest?
d) We mentioned that ∆H vap can be related to intermolecular interactions. This leads
to an interesting observation when looking at ∆H vap for a homologous series at a
specific temperature, namely that the rate of increase is constant with increasing
carbon number. So if we were to look at the series of homologous linear alkanes (i.e.,
ethane, propane, n-butane, ...) and compute ∆H vap , we would find that the increase
in going from ethane to propane is the same as the increase in going from propane to
n-butane and so on. This constant rate of increase is equivalent to the contribution of
a –CH2 – methyl unit. Cool!
Compute ∆H vap for ethane, propane, n-butane, n-pentane, n-hexane and n-heptane
at 300 K. Is the rate of increase (approximately) constant? Using the values of ethane
and propane, could you predict the value of n-butane? How about the value of npentane? How good are the predictions?
2.14 Exercises
47
Exercise 2.3 “Yaws’ Handbook of Properties of Aqueous Systems”3 recommends the
following equation to compute the solubility of organic gases in water:
log10 S = A +
B
+C log10 T
T
(2.6)
where S is the solubility in water in parts per million by weight (ppm), T is the temperature
in K, and A, B , and C are regressed coefficients.
Let s be the solubility in water in parts per million by moles. We can convert from
S to s using the molecular weight of the solute (M s ) and the molecular weight of water
(M w ) as:
s=
S/M s
¡
¢
S/M s + 1 × 106 − S /M w
(2.7)
The molecular weight of water is 18.015 g/mol, and the molecular weight of the solutes
are provided in the table below.
compound
methane
ethane
propane
Ms
16.043
30.070
44.097
A
41.2703870202563
−67.5454935533925
−116.868391911852
B
−1055.8685134604
4033.20796737051
6267.69431176702
C
−14.6882884263499
22.5339742887131
39.4739999999982
Tmin [K ]
273.15
275.15
283.15
Tmax [K ]
360.95
323.15
360.95
a) Write a script to compute the solubility using the expression suggested by Yaws’ in
units of ppm by weight and by mole (S and s).
b) Using your script, compute the solubility of methane, ethane, and propane at a range
of temperatures. Does the solubility of a gas in water increase or decrease with
increasing temperature?
c) Compare the solubility in ppm by mole (s) of methane, ethane, and propane at
a common temperature of 298.15 K. Does the solubility increase or decrease with
increasing alkyl chain length? Put differently, does the solubility increase or decrease
in going from methane to ethane to propane?
Exercise 2.4 In your transport phenomenon course (fluid mechanics), you likely solved
many problems that required you to read values of friction constants from a Moody
chart, or to use analytic expressions for the friction factor of limited range. Recently,
Díaz-Damacillo and Plascencia published an article in AIChE Journal titled: “A New Six
Parameter Model to Estimate the Friction Factor.”4 In that work, the authors propose
a new analytic expression containing six parameters that is capable of estimating the
friction factor for flow in pipes at all conditions (i.e., Reynold’s numbers and relative
roughness). The proposed expression takes the form:
f =
64
λ1
λ2
³
´+
³
+
Re 1 + exp τ1 −Re
1 + exp τ2 −Re ·
100
600
²
D
´
(2.8)
where f is the friction factor, Re is the Reynold’s number, defined as:
3 Yaws, Carl L. (2012). Yaws’ Handbook of Properties of Aqueous Systems. Knovel. Retrieved from https://app.knovel.com/hotlink/toc/id:kpYHPAS006/yaws-handbook-properties/
yaws-handbook-properties
4 L. Díaz-Damacillo and G. Plascencia, AIChE J. 2019, 65, 1144–1148. DOI: 10.1002/aic.16535
48
Chapter 2 Scripts
Re =
ρV D
µ
where ρ is the density of the fluid, V is the fluid flow velocity, and D is the diameter of the
pipe. The term ² is the pipe roughness, and the term ²/D is dimensionless and commonly
referred to as the relative roughness. In addition to Re and ²/D (two parameters), the other
six parameters are λ1 , λ2 , τ1 , and τ2 . The parameter λ1 is the residual stress contribution
from the laminar to turbulant transition to the friction factor, λ2 is the residual stress
contribution from the pipe roughness to the friction factor, τ1 is the value of Re at which
occurs the first transition in the friction factor, and τ2 is the value of Re at which the
second transition occurs. The values of λ1 and τ1 are constant and equal to:
λ1 = 0.02
τ1 = 3000
and λ2 and τ2 are given by the following expressions:
!2 ¯
¯
¯
¢
¯
²
1
¯
·
3.7065 D
¯
Ã
¯
1
¯
λ2 = ¯λ1 −
¡
¯
−2 log
10
0.77505 10.984
τ2 = ¡ ¢2 − ¡ ² ¢ + 7953.8
²
D
D
As you try to keep track of units, remember that Re and ²/D are dimensionless.
a) Write a script that computes f for known values of Re and ²/D. To test your code, for
Re = 1 × 105 with ²/D = 1/30 I get f = 0.0601.
b) Using your script, does f increase or decrease when Re increases?
c) Using your script, does f increase or decrease when ²/D increases?
Chapter
3
for loops and basic plotting
In Chapter 3 we continue to build our foundational knowledge of MATLAB with
the introduction of loops and plotting. By the end of this chapter you will be able
to:
• Define a for loop and explain how it works
• Explain how to construct a for loop to automate repetitive calculations
• Demonstrate the ability to construct basic plots
• Apply the use of for loops and plotting to solve basic engineering problems
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
3.1 for loops
A loop is a part of a program that executes repeatedly; a for loop is the kind of
loop that uses the for statement.
The simplest use of a for loop is to execute one or more lines a fixed number of times. For example, in Example 2.3 (page 33) we wrote a script named
car_update (Listing 2.4) that simulates one week in the life of a rental car company. To simulate an entire year, we have to run it 52 times:
for i=1:52
car_update
end
t Let’s interpret the for loop
another way. For the case
1 ≤ i ≤ 52, execute the
command(s) between the
for and end statements.
Initially, when we first encounter the for statement,
i is assigned a value of 1 (or
whatever integer you set the
lower-bound to be). Then
we execute car_update
and reach end. Here, the
computer asks if i=52 (or
whatever integer you set
the upper-bound of the list
to be). If i=52 we exit and
move on in the code. However, if i<52, we go back to
for, increment the value of
i by one (i = i + 1), then
execute car_update, and
then ask again if i=52. This
repeats until i=52 when the
end statement is reached.
t We previously saw that
The first line looks like an assignment statement, and it is like an assignment
statement, except that it runs more than once. The first time it runs, it creates the
variable i and assigns it the value 1. The second time, i gets the value 2, and so
on, up to 52.
49
adding a semi-colon (;) at
the end of a line suppresses
MATLAB from printing the
result to screen. A semicolon is not necessary here
for the for and end lines.
Chapter 3 for loops and basic plotting
50
The colon operator, :, specifies a range of integers. In the spirit of unit testing,
you can create a range at the prompt:
>> 1:5
ans = 1
t When writing a for loop
from command line, you
do not need to press
Shift + Enter to get to the
next line. MATLAB recognizes that the loop is “open”,
and will not evaluate until
you press Enter after the
end statement. Remember
MATLAB skips whitespace,
so I add spaces to make my
command more readable.
2
3
4
5
The variable you use in the for statement is called the loop variable. It is a
common convention to use the names i, j and k as loop variables.
The statements inside the loop are called the body. By convention, they are
indented to show that they are inside the loop, but the indentation does not
actually affect the execution of the program. The end of the loop is officially
marked by the end statement.
Let’s look at a simple example that we can run from the command line that
displays the value of the loop variable:
>> for i=1:5
i
end
i
i
i
i
i
=
=
=
=
=
1
2
3
4
5
Does the result make sense? This simple example allows us to test our understanding of the flow of calculations in a for loop, and is a perfect example of
using the Command Window for unit testing and debugging.
What is the value of i after the loop is complete? Let’s unit test from the
command line to find out:
>> i
i = 5
The current value of i is the last value assigned before exiting the loop. The great
thing about computers is they do exactly what you tell them to. The bad thing
is they do exactly what you tell them to, even if it is wrong. So take time to fully
understand the flow of calculations through a for loop. Please try to get in the
habit of unit testing from the command line. It is an invaluable tool to help you
check your understanding of MATLAB.
As this example shows, you can run a for loop from the command line, but
it’s much more common to put it in a script. If this discussion has been unclear,
please watch the available screen cast.
When learning about a new feature of MATLAB, it is always a great idea to look
3.1 for loops
51
at the MATLAB documentation pages. Here, try doc for. For this specific case,
the documentation will use features we haven’t yet discussed in this class, such as
vectors and matrices. But with time it will. You will also get an idea of some more
advanced capabilities that we will take advantage of later, once we have honed
our skills a little more, such as break and continue. The documentation is great
too because it will link to related commands and keywords.
Example 3.1
Create a script named car_loop that uses a for loop to run car_update (Listing 2.4 on page 2.4) 52 times. Remember that before you run car_update, you
have to assign values to a and b. For this exercise, start with the values a = 150
and b = 150. Also, make sure that both car_update and car_loop are in your
current path so that they can “see” each other.
If everything goes smoothly, your script will display a long stream of numbers on
the screen. But it is probably too long to fit, and even if it fits, it would be hard to
interpret. A graph would be much better!
(Running, I get a final answer of b = 184 and a = 116.)
Solution: (Link to scree cast with accompanying M-files.) The script is a straightforward for loop. We want to run the script car_update 52 times to simulation
the passage of cars over a period of 1 year or 52 weeks.
Listing 3.1 car_loop.m
1
2
3
4
5
6
7
8
9
10
11
%
% Update the number of cars in Albany and Boston over the
% course of 52 weeks.
% Precondition: you must assign the number of cars in Albany and Boston
%
at the start of the week to a and b, respectively.
% Postcondition: a and b have been modified to reflect the number of
%
cars that moved after 1 year (52 weeks).
%
for i=1:52
car_update
end
In the interest of space I will not display the output here, but the script would be
run as:
>> b = 150;
>> a = 150;
>> car_loop
Note that since car_loop references the script car_update, it is important that
both scripts are in the same directory, and in your current path. Running the script,
I get a final answer of b = 184 and a = 116. In the next section, we will see how
the results may be plotted.
Chapter 3 for loops and basic plotting
52
In the interest of checking our understanding, in Example 2.3 (page 33), we simulated the passage of three weeks. Let’s write a script car_loop_short to reproduce
the results.
Listing 3.2 car_loop_short.m
1
2
3
4
5
6
7
8
9
10
11
%
% Update the number of cars in Albany and Boston over the
% course of 52 weeks.
% Precondition: you must assign the number of cars in Albany and Boston
%
at the start of the week to a and b, respectively.
% Postcondition: a and b have been modified to reflect the number of
%
cars that moved after 3 weeks.
%
for i=1:3
car_update
end
>> b = 150;
>> a = 150;
>> car_loop_short
b = 153
a = 147
b = 155
a = 145
b = 157
a = 143
Nice! This will be rather primitive and not as pretty as MATLAB is capable, but we
could use disp to indicate the week to make the output more readable by a user.
Listing 3.3 car_loop_short2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
%
% Update the number of cars in Albany and Boston over the
% course of 52 weeks.
% Precondition: you must assign the number of cars in Albany and Boston
%
at the start of the week to a and b, respectively.
% Postcondition: a and b have been modified to reflect the number of
%
cars that moved after 3 weeks.
%
for i=1:3
disp('Week: ')
disp(i)
car_update
end
>> b = 150;
>> a = 150;
>> car_loop_short2
3.2 plotting
Week:
1
b = 153
a = 147
Week:
2
b = 155
a = 145
Week:
3
b = 157
a = 143
Keeping the end user in mind when writing code is always a good idea. You might
also imagine a need to come back and run your code a year from now. Additionally,
remember that I have eliminated the extra lines MATLAB produces in its output,
so the actual result is actually less pretty than I have shown.
3.2 plotting
plot is a versatile function for plotting points and lines on a two-dimensional
graph. Unfortunately, it is so versatile that it can be hard to use (and hard to read
the documentation!). We will start simple and work our way up.
To plot a single point, type
>> plot(1, 2)
A Figure Window should appear with a graph and a single, blue dot at x position
1 and y position 2. To make the dot more visible, you can specify a different shape:
>> plot(1, 2, 'o')
The letter in single quotes is a string that specifies how the point should be plotted.
You can also specify the color:
>> plot(1, 2, 'ro')
r stands for red; the other colors include green, blue, cyan, magenta, yellow and
black. Other shapes include +, *, x, s (for square), d (for diamond), and ^ (for a
triangle).
When you use plot this way, it can only plot one point at a time. If you run
plot again, it clears the figure before making the new plot. The hold command
53
Chapter 3 for loops and basic plotting
54
lets you override this behavior. hold on tells MATLAB not to clear the figure
when it makes a new plot; hold off returns to the default behavior.
Try this:
>> hold on
>> plot(1, 1, 'o')
>> plot(2, 2, 'o')
When you first execute hold on, a blank Figure Window should appear. Next, the
point (1,1) will be plotted as a circle, and then the point (2,2) will be plotted as
a circle; by default, since no color was specified, the first point (or data set) will
be colored blue, and the second will be colored red. MATLAB scales the plot
automatically; in this example the points are plotted in the corners. You can manually fix the x- and y-axis range using the commands xlim and ylim, respectively.
Keeping Figure Window open, try this:
>> hold on
>> xlim([-1,3])
>> ylim([0,4])
t We will learn later that the
argument provided to xlim
and ylim is actually a (row)
vector of length 2.
What happened? We just changed the x-axis range to go from –1 to 3, and the
y-axis range to go from 0 to 4.
If you close the Figure Window you will need to start over again; you will need
to again execute hold on to plot more than one point (or data set). Alternatively,
to begin working with new data, you can keep the Figure Window open and just
clear the data using clf; since the Figure Window never closed, there is no need
to re-execute hold on.
In the next example, we will additionally look at adding a figure title, axis
labels, and a legend. As I stated before with for, you should get in the habit of
having a look at the MATLAB documentation pages whenever we learn about a
new MATLAB feature. Here, try doc plot. You will see a range of examples and
learn about other features, such as creating sub-plots.
If this discussion has been unclear, please watch the available screen cast.
Example 3.2
Modify car_loop (Listing 3.1) so that each time through the loop it plots the value
of a versus the value of i (your loop variable).
Once you get that working, modify it so it plots the values of a with red circles and
the values of b with blue diamonds.
One more thing: if you use hold on to prevent MATLAB from clearing the figure,
you might want to clear the figure yourself, from time to time, with the command
clf.
3.2 plotting
55
Solution: (Link to screen cast with accompanying M-file.) So we are asked to
update car_loop (Listing 3.1) so that it now plots the results.
Listing 3.4 car_loop2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
%
%
%
%
%
%
%
%
Update the number of cars in Albany and Boston over the
course of 52 weeks and plot the results.
Precondition: you must assign the number of cars in Albany and Boston
at the start of the week to a and b, respectively.
Postcondition: a and b have been modified to reflect the number of
cars that moved after 1 year (52 weeks).
% I will start by clearing the graph in case anyting is already printed.
% Note that if a figure window is not already open, a blank figure
% window will appear.
clf
% Execute hold on to allow plotting more than one point.
% Note, I said previously if a figure was already open, clf would
% clear the existing figure. If we had previously executed hold on,
% we would not need to do so again. However, when in doubt,
% exectute hold on. If hold was already on, it will stay on.
hold on
for i=1:52
car_update
plot(i, a, 'ro')
plot(i, b, 'bd')
end
In the interest of space I again will not display the output from car_update here;
we could instead go back to car_update and suppress printing a and b.
The updated script would be run as:
>> b = 150;
>> a = 150;
>> car_loop2
The resulting figure is:
Chapter 3 for loops and basic plotting
56
190
180
170
160
150
140
130
120
110
0
10
20
30
40
50
60
Figure 3.1 The plot generated by MATLAB upon running the script
car_loop2.
Let’s format the image so that it is presentation ready. Keep the Figure Window open
and execute the commands the follow from the command line. Alternatively, you
can add the commands to the end of car_loop2.m and re-run then script (which
in general would be preferred).
First, let’s add labels to the x- and y-axis. This is achieved with the command
xlabel and ylabel, respectively, where we will pass a string which is the axis
label.
>> xlabel('Week')
>> ylabel('Number of Cars')
Since we are simulating the passage of 1 year, or 52 weeks, lets change the x-axis
range to go from 0 to 52:
>> xlim([0, 52])
Next, let’s add a title. We add a title using the title command, where again we
pass a string which is the plot title.
>> title('Example 3.2')
To finish up, let’s add a legend to distinguish between the cars in Albany and
Boston. Each loop iteration, first we plot the number of cars in Albany (a) and
then Boston (b). To add a legend we use the command legend. To legend we will
pass a series of strings, with each string separated by a comma, in the order of the
data sets plotted. Note, there are many subtleties to adding legends to plots with
3.2 plotting
57
multiple data sets, so do not be discouraged if it does not work the first time.
>> legend('Albany', 'Boston')
The final version of our figure may be found below:
190
Albany
Boston
180
Number of Cars
170
160
150
140
130
120
110
0
10
20
30
40
50
Week
Figure 3.2 Our formatted plot from car_loop2.
Notice that by default MATLAB places the legend in the top right of the figure,
which covers some of our data. This location is not ideal. You could move it by
clicking on the legend and dragging it to the desired location. In general, I prefer to
do everything from the command line. When I use MATLAB at a high performance
computing center, I do not have access to a graphical interface. Working exclusively
from command line also facilitates moving to a MATLAB alternative such as GNU
Octave. You can move the location of the legend from command line by passing an
optional argument to legend, but I will not do so here to keep from overwhelming
you. (If interested, give help legend or doc legend a try. In fact, every time you
learn a new function, checking out MATLAB’s documentation is always a good
idea.)
Lastly, it would be nice to save a copy of your pretty figure. From the Figure
Save As... . You can choose the appropriate image format, and save it to the desired location; your current path is the default location. If
you save your figure as a type “fig” (or a FIG-file to use the MATLAB lingo), it can
be re-opened later in MATLAB and edited. It may be a good idea to save a copy as
a FIG-file and a second copy as an image file you can share and include in your
lab reports. From the command line, saving as FIG-file is accomplished using the
savefig command, and saving as an image file can be accomplished using the
print command. To save the image as FIG-file and a png with name “example_32”
use:
Window, you can use File
>> savefig('example_32.fig')
Chapter 3 for loops and basic plotting
58
>> print('-dpng', 'example_32')
Other formats are available, but again, in the interest of not overwhelming you, we
will stop here. (And again, have a look at MATLAB’s documentation if interested.)
3.3 Sequences
In mathematics a sequence is a set of numbers that corresponds to the positive
integers. The numbers in the sequence are called elements. In math notation,
the elements are denoted with subscripts, so the first element of the series A is
A 1 , followed by A 2 , and so on.
for loops are a natural way to compute the elements of a sequence. As an
example, in a geometric sequence, each element is a constant multiple of the
previous element. As a more specific example, let’s look at the sequence with
A 1 = 1 and the ratio A i +1 = A i /2, for all i . In other words, each element is half as
big as the one before it.
The following loop computes the first 10 elements of A:
a = 1
for i=2:10
a = a/2
end
Each time through the loop, we find the next value of a by dividing the previous
value by 2. Notice that the loop range starts at 2 because the initial value of a
corresponds to A 1 , so the first time through the loop we are computing A 2 .
Each time through the loop, we replace the previous element with the next,
so at the end, a contains the 10th element. The other elements are displayed on
the screen, but they are not saved in a variable. Later, we will see how to save all
of the elements of a sequence in a vector.
This loop computes the sequence recurrently, which means that each element depends on the previous one. For this sequence it is also possible to
compute the i th element directly, as a function of i , without using the previous
¡ ¢i −1
element. In math notation, A i = A 1 12
.
Example 3.3
Write a script named sequence that uses a loop to compute elements of A directly.
Solution: (Link to screen cast and accompanying M-file.)
Let me actually write a script named sequence that uses a loop to compute the
elements of A both directly and recurrently. This will allow me to make sure my
code is correct. For the recurrent calculations, I will use variable b instead of a.
3.4 Series
59
Listing 3.5 sequence.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
%
%
%
%
%
%
%
%
Calculating the value of Ai+1 = Ai/2 where A1 = 1 both directly
and recurrently. The recurrent calculation will not be printed, but
used to check the correctness of the direct calculations.
Precondition: none
Postcondition: at the conclusion of the loop a will contain the
value of A10
% a1 for use in the direct calculations
a1 = 1;
% initial value of b for the recurrent calculations
b = 1;
%
for i=2:10
b = b/2;
a = a1*0.5^(i-1)
% Checking that both compute the same value
check = b - a
end
3.4 Series
In mathematics, a series is the sum of the elements of a sequence. It’s a terrible
name, because in common English, “sequence” and “series” mean pretty much
the same thing, but in math, a sequence is a set of numbers, and a series is an
expression (a sum) that has a single value. In math notation, a series is often
P
written using the summation symbol .
For example, the sum of the first 10 elements of A is
10
X
Ai
i =1
where A i = A 1
series:
¡ 1 ¢i −1
2
. A for loop is a natural way to compute the value of this
a1 = 1;
total = 0;
for i=1:10
a = a1 * 0.5^(i-1);
total = total + a;
end
ans = total
Chapter 3 for loops and basic plotting
60
a1 is the first element of the sequence. Each time through the loop a is the i th
element. Note we can start with i=1 because we are computing the elements
directly, and the first time through the loop, i=1 resulting in a=a1. Had we been
t When using a for loop to
compute a sum, the accumulator will almost always
start with a value of 0. This
way when we add our first
term to the sum, the sum is
equal to the first term. Likewise, when we later use for
loops to compute continuous products, we will start
with a value of 1
calculating recurrently, we would need to specify a starting point, as we will see
in the next Example.
The way we are using total is sometimes called an accumulator; that is, a
variable that accumulates a result a little bit at a time. Before the loop we initialize
it to 0. Each time through the loop we add in the i th element. At the end of the
loop, total contains the sum of the elements. Since that’s the value we were
looking for, we assign it to ans.
Example 3.4
In this section, we wrote a for loop to compute the sum of the sequence (or series),
where each element was computed directly. Write a script named series_direct
to perform the calculation. Next, write a script named series_recurrent to
compute the same sum with the elements computed recurrently. You will have to
be careful about where you start and stop the loop.
(ans = 1.9980)
Solution: (Link to screen cast and accompanying M-files.)
We will create two scripts. First, let’s create a script series_direct that calculates
the terms of the sequence directly. Then, let’s create a script series_recurrent
that calculates the terms of the sequence recurrently. This will allow us to test the
second case for correctness.
3.5 Generalization
61
Listing 3.6 series_direct.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
%
%
%
%
%
%
%
Calculating the value of the series of Ai from 1 to 10
with each element of the sequence computed directly.
Precondition: none
Postcondition: at the conclusion of the loop total and ans
will contain the value of the sum
a1 = 1;
total = 0;
for i=1:10
a = a1 * 0.5^(i-1);
total = total + a;
end
ans = total
Listing 3.7 series_recurrent.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
%
%
%
%
%
%
%
Calculating the value of the series of Ai from 1 to 10 with
each element of the sequence computed recurrently.
Precondition: none
Postcondition: at the conclusion of the loop total and ans
will contain the value of the sum
a = 1;
% Start with total = a1
total = a;
% Then we begin the loop with a2
for i=2:10
a = a/2;
total = total + a;
end
ans = total
When executed, both scripts properly compute ans = 1.9980. Note that in
series_direct, we could start with i=2, but then total needs to begin with
(or be initialized with) total=a1.
3.5 Generalization
As written, the previous example always adds up the first 10 elements of the
sequence, but we might be curious to know what happens to total as we increase
the number of terms in the series. If you have studied geometric series, you might
know that this series converges on 2; that is, as the number of terms goes to
infinity, the sum approaches 2 asymptotically.
Chapter 3 for loops and basic plotting
62
To see if that’s true for our program, series_direct, we could replace the
constant, 10, with a variable named n:
a1 = 1;
total = 0;
for i=1:n
a = a1 * 0.5^(i-1);
total = total + a;
end
ans = total
Now the script can compute any number of terms, with the precondition that
you have to set n before you execute the script. Here’s how you could run it with
different values of n, where I use format long to display more decimal places:
t We haven’t previously seen
multiple commands on
a single line like we have
here. Multiple commands
separated by semi-colon
can be placed on a single
line rather than a separate
line for each command. In
series_direct the final
sum in printed to screen;
this therefore must end a
line.
>> format long
>> n=10; series_direct
total = 1.99804687500000
>> n=20; series_direct
total = 1.99999809265137
>> n=30; series_direct
total = 1.99999999813735
>> n=40; series_direct
total = 1.99999999999818
It sure looks like it’s converging on 2.
Replacing a constant with a variable is called generalization. Instead of
computing a fixed, specific number of terms, the new script is more general;
it can compute any number of terms.
This is an important idea we will come back to when we talk about functions.
3.6 Examples
63
3.6 Examples
Practice makes perfect...
Example 3.5
Write a general script that uses a for loop to compute the factorial of an arbitrary
integer n. Test you script by computing the factorial of a number and comparing
to the result of the MATLAB function factorial.
The recall factorial of 3 is:
3! = 3 · 2 · 1
or equivalently
3! = 1 · 2 · 3
I write this second expression as a hint as to how you might set-up a for loop. In
general,
n! = n · (n − 1) · (n − 2) · ... · 1
or
n! = Πni=1 i
where Π is the symbol for a continuous product. (A continuous product is similar
to a sum, only we multiple consecutive elements rather than add them.)
Solution: (Link to screen cast and accompanying M-file.)
Listing 3.8 Example_3_5.m
1
2
3
4
5
6
7
8
9
10
% Calculating the facotrial of a number n
%
% Pre-condition: n, the number you wish to compute the factorial of
% Post-condition: ans and n_fact will contain the value of the factorial
%
of n
n_fact = 1;
for i = 2:n
n_fact = n_fact*i;
end
ans = n_fact
Note that I could start my loop with either i=1 or i=2 and obtain the same result.
When computing sums with a for loop, we typically initialize the counter variable
with a value of zero. A number plus zero is equal to that number. With continuous
products, we typically initialize the counter variable with a value of one. A number
times one is equal to that number.
Chapter 3 for loops and basic plotting
64
Example 3.6
During class, we found that sin(pi) was not exactly equal to zero as we expected.
The reason for this is pi is not exact but is rather a numerical approximation. It is
valuable to know how MATLAB actually computes sine, and how the numerical
error is thus propagated through the calculation.
The calculation of sine is not a standard operator. The MATLAB function is actually
performing a Taylor series expansion about the specified point:
sin (x) =
∞
X
i =0
(−1)i
x 2i +1
(2i + 1)!
(3.1)
MATLAB can not carry out the summation to ∞, so it must truncate the series. This
introduces additional numerical error in the calculation, although the number of
terms used is large enough that you likely do not notice. This is in addition to the
numerical error of approximating pi, which is carried throughout the sum.
Write a script to compute sin(pi) using a Taylor series expansion, and compare
to the MATLAB function sin. Run your script multiple times to see how the result
changes with the number of terms in the series. Note that eq. (3.1) requires the
calculation of the factorial of 2i + 1. You can add a second, “nested” loop to your
script to compute the factorial, just be sure to use a unique loop variable.
Solution: (Link to screen cast and accompanying M-file.)
Listing 3.9 Example_3_6.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
% Calculating sin(x) using a Taylor series expansion
%
% Pre-condition: x in radians, where x is what you would like to compute
%
the sine of, and n, the number of terms in the Taylor
%
series expansion
% Post-condition: The value of sin(x) using n terms in the series will
%
be stored to taylor_ans, and the result using MATLAB's
%
built in function will be stored to ans_matlab. I
%
will also plot the value of sin(x) vs the terms in the
%
sum.
%
% Start by clearing our figure
clf
% Don't clear my graph when I add a new point
hold on
% My Taylor series accumulator
taylor_ans = 0;
for i=0:n
% We will use 2*i+1 twice, so let's precompute
dum = 2*i+1;
% Computing the factorial of 2*i+1
% Note that when i = 0, it won't perform the for loop. This is okay
% since the factorial of 1 is 1.
n_fact = 1;
3.6 Examples
for j=2:dum
n_fact = n_fact*j;
end
% Calculating the ith Taylor term
taylor_term = (-1)^(i)*x^(dum)/n_fact;
% Updating my Talylor sum
taylor_ans = taylor_ans+taylor_term;
plot(i,taylor_ans,'ro')
end
% Labeling the plot axis
xlabel('n')
ylabel('sin(x)')
taylor_ans
ans_matlab = sin(x)
1.6
1.5
1.4
1.3
sin(x)
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
65
1.2
1.1
1
0.9
0
5
10
15
20
n
Figure 3.3 A plot of the estimate of sin(x) as a function of terms, n, in the
Taylor series, where here x = π/2..
Chapter 3 for loops and basic plotting
66
Example 3.7
We have already seen the Fibonacci sequence, F , which is defined recurrently as
F i = F i −1 + F i −2
In order to get started, you have to specify the first two elements, but once you
have those, you can compute the rest. The most common Fibonacci sequence
starts with F 1 = 1 and F 2 = 1. Write a script that uses a for loop to compute the
first 10 elements of this Fibonacci sequence. As a postcondition, your script should
assign the 10th element to ans.
Now generalize your script so that it computes the n th element for any value of
n, with the precondition that you have to set n before you run the script. To keep
things simple for now, you can assume that n is greater than 2.
Hint: you will have to use two variables to keep track of the previous two elements
of the sequence. You might want to call them prev1 and prev2. Initially, prev1
= F1 and prev2 = F2 . At the end of the loop, you will have to update prev1 and
prev2; think carefully about the order of the updates!
(ans = 55)
Solution: (Link to screen cast and accompanying M-file.)
Listing 3.10 Example_3_7.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% Computing the nth Fibonacci number. For now, we will assume
% that n is greater than 2. Later in Chapter 4, we will find
% that we can use an if statement to check this condition.
%
% Pre-condition: n, the value of the desired Fibonacci number (n>2)
% Post-condition: ans and Fi contain the value of the nth Fibonacci number
%
F1 = 1; % F(i-2)
F2 = 1; % F(i-1)
for i=3:n
Fi = F2 + F1;
% now thinking about what I need for my next time through the
% loop (for i+1)
prev_1 = Fi; % current i will be i-1
prev_2 = F2; % current i-1 will be i-2
F1 = prev_2;
F2 = prev_1;
end
ans = Fi
Example 3.8
Write a script that loops i through a range from 1 to 20, uses the script from
Example 3.7 to compute Fibonacci numbers, and plots F i for each i with a series
of red circles.
3.6 Examples
67
Solution: (Link to screen cast and accompanying M-file.)
Listing 3.11 Example_3_8.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
% Plotting the first 20 Fibonacci numbers
%
% Pre-condition: none
% Post-condition: plot of the first 20 Fibonacci numbers
% Clear figure
clf;
% Don't clear graph with each new point
hold on
F1 = 1; % F(i-2)
F2 = 1; % F(i-1)
plot(1,F1,'ro')
plot(2,F2,'ro')
for i=3:20
Fi = F2 + F1;
% now thinking about what I need for my next time throuh the
% loop (for i+1)
prev_1 = Fi; % current i will be i-1
prev_2 = F2; % current i-1 will be i-2
F1 = prev_2;
F2 = prev_1;
plot(i,Fi,'ro')
end
xlabel('i')
ylabel('F_i')
Chapter 3 for loops and basic plotting
68
7000
6000
5000
Fi
4000
3000
2000
1000
0
0
5
10
15
20
i
Figure 3.4 A plot of the Fibonacci sequence versus index (i ).
3.7 CPB Examples
69
3.7 CPB Examples
Example 3.9
Let’s build-upon the script you wrote for Example 2.4 on page 38. Recall you
wrote a script to calculate the vapor pressure (p sat ) of ethanol for a given T , where,
A = 8.13484, B = 1662.48, and C = 238.131, and is valid over the range −114.1 ◦ C <
T < 243.1◦ C, where T is in ◦ C and p sat is in mmHg.
Here, let’s create a plot of our results. A common way to represent the pT plane is
using a Clapeyron plot. In a Clapeyron plot, we plot log10 p sat or ln p sat versus 1/T .
Since it is inverse T , T should be in absolute units (e.g., K). Create a Clapeyron plot
for ethanol over the range 0 to 100 ◦ C, where p sat is in units of mmHg and T is in
units of K. Represent each point with a circle. Label your axis and give your plot a
title.
Solution: (Link to screen cast and accompanying M-file.)
Over small temperature ranges, ln p sat or log p sat vs 1/T is linear. This is why it
is common to plot p sat vs T is this way. This type of plot is know as a Clapeyron
plot, named after the thermodynamicist that first suggested the linear dependence.
Linearizing data is advantageous as it facilitates interpolating and extrapolating
the results. For this case there is also a physical benefit, but you will have to wait
for your thermodynamics class for that discussion.
Listing 3.12 clapeyron_plot.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
%
%
%
%
%
%
%
%
Plotting the vapor pressure of ethanol using Antione's equation
from 0 to 100 oC
Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
oC) stored to variables a, b, and c.
Post-condition: Plot of log10 p vs 1/T where T is in units of K
and p is in units of mmHg
% Start by clearing our figure
clf
% Don't erase the graph when we add a new data set
hold on
% We need to calculate log10 p over the range 0 to 100 C.
% We will loop from 0 to 100 C in increments of 1 C.
% Each interation we will plot the results, where
% the temperature in converted to K before calculating
% the inverse.
for t_C = 0:100
% Calcutating log_p_mmHg, p in units of mmHg
log_p_mmHg = a-b/(t_C+c);
% Calculating 1/t with t in units of K
tinv = 1/(t_C+273.15);
% Plotting
plot(tinv,log_p_mmHg,'ro')
end
Chapter 3 for loops and basic plotting
70
% labeling the axis
xlabel('1/T [1/K]')
ylabel('log10 p/mmHg')
% plot title
title('Clapeyron plot for ethanol for 0 to 100 C')
% Last, let's get in the habit of saving and printing our graphs
savefig('ethanol_clapeyron_plot.fig')
print('-dpng', 'ethanol_clapeyron_plot')
>>
>>
>>
>>
a = 8.13484;
b = 1662.48;
c = 238.131;
clapeyron_plot
3.5
Clapeyron plot for ethanol for 0 to 100 C
3
log10 p/mmHg
28
29
30
31
32
33
34
35
36
37
38
2.5
2
1.5
1
2.6
2.8
3
3.2
1/T [1/K]
3.4
3.6
3.8
×10 -3
Figure 3.5 The plot generated by MATLAB upon running the script
clapeyron_plot.
Note, here I require a, b, and c as pre-conditions, despite indicating throughout
the script that it is for ethanol. It would in general be better to assign values to a, b,
and c within the script. This would give me a record of exactly what was done, in
addition to being inline with our ethanol specific script. It would then be easy to
update the script for a new compound and just rename the file. However, I did not
do this to give us practice specifying pre-conditions.
3.7 CPB Examples
71
Example 3.10
Let’s build upon the script you wrote for Example 2.5 on page 41. Recall you wrote
a script to calculate the average speed of a molecule at a specified temperature.
You used your script to compare the average speed of water and ethanol at 0 ◦ C.
Create a plot of the average speed of water and ethanol over the range 0 to 100 ◦ C.
Use a different symbol to represent water and ethanol. Label your axis, give your
plot a title, and add a legend.
Adding a legend can be tricky. Things will become much easier when we learn
about vectors, but we are not there yet. Every time you plot a new point, MATLAB
treats this as a new set of data. The legend labels need to be provided in the same
order as the data sets that have been plotted. So if you plot a point for water first
and then ethanol, you are all set. But if you plot all of the water data and then all of
the ethanol data, it will not work as expected. Why, because the first two sets of
data correspond to the first two water points. Give it a try both ways and you will
see what I mean.
Solution: (Link to screen cast and accompanying M-file.)
Maxwell-Boltzmann fun!
Listing 3.13 maxwell_plot.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
%
%
%
%
%
%
%
%
Plotting the average speed of water and ethanol molecules over the
range 0 to 100 oC
Pre-condition: None. I will hardcode everything since we
are asked for a specific temperature range and
the two species are specified.
Post-condition: Plot of average molecular speed versus temperature
% Start by clearing our figure
clf
% Don't erase the graph when we add a new data set
hold on
% gas constant in J/(mol K)
r = 8.314;
% Molecular weight of water and ethanol in kg/mol
mw_water = 18/1000;
mw_ethanol = 46/1000;
% Looping over the range 0 to 100 C and calculating the
% average molecular speed. Note our Maxwell-Boltzmann
% equation uses K for temperature, so we will perform
% the conversion each iteration.
for t_C = 0:100
% Temperature in K
t_K = t_C + 273.15;
% Speed of water in m/s
speed_water = sqrt( 3*r*t_K/mw_water );
speed_ethanol = sqrt( 3*r*t_K/mw_ethanol );
% Plotting
Chapter 3 for loops and basic plotting
72
end
% When plotting, we will use the temperature in C, since
% that is what the problem statement asked for.
% Water then ethanol
plot(t_C,speed_water,'ro')
plot(t_C,speed_ethanol,'bx')
% labeling the axis
xlabel('T [C]')
ylabel('speed [m/s]')
% plot title
title('Average speed from Maxwell-Boltzmann')
% legend.
% Data is plotted water then ethanol
legend('water','ethanol')
% Last, let's get in the habit of saving and printing our graphs
savefig('maxwell_plot.fig')
print('-dpng', 'maxwell_plot')
>> maxwell_plot
Average speed from Maxwell-Boltzmann
750
water
ethanol
700
650
600
speed [m/s]
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
550
500
450
400
350
0
20
40
60
80
100
T [C]
Figure 3.6 The plot generated by MATLAB upon running the script
maxwell_plot.
3.8 Creating multiple figures
3.8 Creating multiple figures
A question that has come up repeatedly in the past is how to create multiple
figures. This is best demonstrated using Example 3.10 where we are plotting two
sets of data. So what exactly do I mean by creating multiple figures? Imagine I
wish to create a separate plot for water and ethanol, and I wish to compare them.
We could do this by opening a figure window and first generating the plot for water.
We can then save and print the plot. Then we would clear the figure, generate the
plot for ethanol, and then save and print again. We could then compare the two
printed plots. By default MATLAB only uses a single figure window, which is why
we are forced to compare the printed files.
What would be great is if we could create multiple figure windows. This can
be accomplished using the figure command. If we type figure(1), it will open
a new figure window named Figure 1. This can be followed with the hold on
command, and we can then go ahead and plot our data for water. We can open
up a second, new figure window using the command figure(2). This new figure
window will be named Figure 2. This is a new figure window, so you will need
to re-type hold on, and then you can plot your ethanol data.
To switch back and forth between the figures, you need just type figure(1)
or figure(2), depending on which you are interested in manipulating, followed
by the commands of interest. Let’s have a look at how we can update Listing 3.12
to accomplish this.
Listing 3.14 maxwell_multi_plot.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
%
%
%
%
%
%
%
%
Plotting the average speed of water and ethanol molecules over the
range 0 to 100 oC
Pre-condition: None. I will hardcode everything since we
are asked for a specific temperature range and
the two species are specified.
Post-condition: Plot of average molecular speed versus temperature
% Start by creating two figure windows. We will plot water in
% Figure 1, and ethanol in Figure 2. We will need to initiate with
% hold on in both.
figure(1)
hold on
figure(2)
hold on
% gas constant in J/(mol K)
r = 8.314;
% Molecular weight of water and ethanol in kg/mol
mw_water = 18/1000;
mw_ethanol = 46/1000;
% Looping over the range 0 to 100 C and calculating the
% average molecular speed. Note our Maxwell-Boltzmann
73
Chapter 3 for loops and basic plotting
74
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
% equation uses K for temperature, so we will perform
% the conversion each iteration.
for t_C = 0:100
% Temperature in K
t_K = t_C + 273.15;
% Speed of water in m/s
speed_water = sqrt( 3*r*t_K/mw_water );
speed_ethanol = sqrt( 3*r*t_K/mw_ethanol );
% Plotting
% When plotting, we will use the temperature in C, since
% that is what the problem statement asked for.
% Water then ethanol.
% As mentioned earlier, we will plot water in Figure 1 and
% ethanol in Figure 2
figure(1)
plot(t_C,speed_water,'ro')
figure(2)
plot(t_C,speed_ethanol,'bx')
end
% labeling the axis
% We will repeat for each figure
figure(1)
xlabel('T [C]')
ylabel('speed [m/s]')
figure(2)
xlabel('T [C]')
ylabel('speed [m/s]')
% plot title
figure(1)
title('Average speed of water from Maxwell-Boltzmann')
figure(2)
title('Average speed of ethanol from Maxwell-Boltzmann')
% Last, let's get in the habit of saving and printing our graphs
figure(1)
savefig('water_maxwell_plot.fig')
print('-dpng', 'water_maxwell_plot')
figure(2)
savefig('ethanol_maxwell_plot.fig')
print('-dpng', 'ethanol_maxwell_plot')
>> maxwell_multi_plot
3.8 Creating multiple figures
75
Average speed of water from Maxwell-Boltzmann
720
700
speed [m/s]
680
660
640
620
600
0
20
40
60
80
100
T [C]
Figure 3.7 The first figure for water generated by MATLAB upon running
the script maxwell_multi_plot.
Average speed of ethanol from Maxwell-Boltzmann
450
440
speed [m/s]
430
420
410
400
390
380
0
20
40
60
80
100
T [C]
Figure 3.8 The second figure for ethanol generated by MATLAB upon running the script maxwell_multi_plot.
Additionally, I would encourage you to have a look at help figure and doc
figure. You may also be interested in MATLAB’s subplot, which can create
multiple plots within the same figure window. This can be useful to facilitate
Chapter 3 for loops and basic plotting
76
comparing plots that have a common axis. It is a little more involved so we
will look at it in examples later. But if you are curious, have a look at MATLAB’s
documentation.
3.9 Glossary
loop: A part of a program that runs repeatedly.
loop variable: A variable, defined in a for statement, that gets assigned a different value each time through the loop.
range: The set of values assigned to the loop variable, often specified with the
colon operator; for example 1:5.
body: The statements inside the for loop that are run repeatedly.
sequence: In mathematics, a set of numbers that correspond to the positive
integers.
element: A member of the set of numbers in a sequence.
recurrently: A way of computing the next element of a sequence based on previous elements.
directly: A way of computing an element in a sequence without using previous
elements.
series: The sum of the elements in a sequence.
accumulator: A variable that is used to accumulate a result a little bit at a time.
generalization: A way to make a program more versatile, for example by replacing a specific value with a variable that can have any value.
3.10 Exercises
77
3.10 Exercises
Exercise 3.1 Let’s build-upon the script you wrote for Exercise 2.1 on page 45. Recall you
wrote a script to calculate the surface tension (σ) at saturation for ethanol, propane, and
acetone at a given temperature (T ). We looked at a range of temperatures to understand
if σ increased or decreased with increasing temperature, and we checked to see that σ
approached a value of zero at the critical point.
Here, let’s build off of this. The script we wrote for Exercise 2.1 works such that if we
specify T , it returns the value of σ. This script works and is bug free, so in the spirit of unit
testing and incremental development, let’s keep it relatively untouched and build-off of
it.
a) Create a new script that loops over the entire temperature range of applicability,
computes σ, and then plots σ versus T . Plot the results for both ethanol, propane, and
acetone in the same figure. Be sure to use a different symbol and color for ethanol,
propane, and acetone, add a title, axis labels, and a legend. (While not required for
homework, try and create three separate figures too.) Do your observations agree with
your findings in Exercise 2.1?
b) It can be difficult to compare ethanol, propane, and acetone in the same figure because
their temperature ranges are so different. One strategy to facilitate comparison is to
plot with respect to the reduced temperature (Tr ):
Tr =
T
Tc
(3.2)
where Tc is the critical temperature. Create a copy of your script and update to plot σ
versus Tr , then re-run. Note that plotting in reduced units is also a common practice
as the properties commonly exhibit (near) universal behavior. This is the basis of
corresponding states theory.
In case there is an issue looking back at Exercise 2.1, the expression for σ along with
the parameters are re-produced below. 1 The value of Tmax is Tc .
σ = A + BT +C T 2 + DT 3 + E T 4 + F T 5
Where σ is the surface tension in units of N/m, T is the temperature in K, and A, B , C , D,
E , and F are regressed coefficients.
compound
propane
ethanol
acetone
A × 102
4.9138
0.8673
5.7452
1 Yaws, Carl L..
B × 104
−0.9349
6.6647
0.4625
C × 106
−0.7665
−4.7511
−1.4587
D × 108
0.4318
1.3783
0.5045
E × 1011
−1.1075
−1.9592
−0.8216
F × 1014
1.1416
1.1142
0.5357
Tmin [K ]
86
250
180
Tmax [K ]
369.82
513.9
508.10
(2012; 2013; 2014). Yaws’ Critical Property Data for Chemical Engineers
and Chemists. Knovel. Retrieved from https://app.knovel.com/hotlink/toc/id:kpYCPDCECD/
yaws-critical-property/yaws-critical-property
Chapter 3 for loops and basic plotting
78
Exercise 3.2 Let’s build upon Exercise 2.2. “Yaws’ Critical Property Data for Chemical
Engineers and Chemists” recommends use of a modified Watson equation to calculate
the enthalpy of vaporization as a function of temperature:
¶
µ
T n
∆H vap = A 1 −
B
(3.3)
where A, B and n are regressed coefficients, T is the temperature in K, and ∆H vap is
the enthalpy of vaporization in units of kJ/mol. Below I have expanded the table of
parameters from Exercise 2.2 for the homologous series of linear alkanes. (Specifically, I
have eliminated ethanol and added n-octane.)
compound
ethane
propane
n-butane
n-pentane
n-hexane
n-heptane
n-octane
A
21.3420
26.8896
33.0198
39.8543
45.61
49.73
59.0771
B
305.42
369.82
425.18
469.65
507.43
540.26
568.83
n
0.403
0.365
0.377
0.398
0.401
0.386
0.439
Tmin [K ]
90.35
85.44
134.86
143.42
177.84
182.56
216.38
Tmax [K ]
305.42
369.82
425.18
469.65
507.43
540.26
568.83
Write a script to compute ∆H vap for each compound at 300 K, and create a plot of
∆H
versus carbon number. Add a title, label your axis, and print the resulting plot to
file. Does ∆H vap appear to increase linearly with the carbon number?
vap
Exercise 3.3 Let’s build upon Exercise 2.3. “Yaws’ Handbook of Properties of Aqueous
Systems”2 recommends the following equation to compute the solubility of organic gases
in water:
log10 S = A +
B
+C log10 T
T
(3.4)
where S is the solubility in water in parts per million by weight (ppm), T is the temperature
in K, and A, B , and C are regressed coefficients.
compound
methane
ethane
propane
Ms
16.043
30.070
44.097
A
41.2703870202563
−67.5454935533925
−116.868391911852
B
−1055.8685134604
4033.20796737051
6267.69431176702
C
−14.6882884263499
22.5339742887131
39.4739999999982
Tmin [K ]
273.15
275.15
283.15
Tmax [K ]
360.95
323.15
360.95
Write a script to compute log10 S for methane over the entire temperature range of
applicability (Tmin to Tmax ), and plot log10 S versus temperature. Add a title and axis
labels. Also, have your script save and print your figure.
2 Yaws, Carl L. (2012). Yaws’ Handbook of Properties of Aqueous Systems. Knovel. Retrieved from https://app.knovel.com/hotlink/toc/id:kpYHPAS006/yaws-handbook-properties/
yaws-handbook-properties
3.10 Exercises
Exercise 3.4 Let’s build upon Exercise 2.4. Build upon your script to plot f versus Re
for a given value of ²/D. That is, make your own Moody chart. Cool! For the purpose of
this question, write a script where you loop over Re values over the range of 1 × 103 to
1.1 × 103 , for each Re compute f for the case of ²/D = 0.1 and ²/D = 0.001, and plot. Plot
the results for ²/D = 0.1 and ²/D = 0.001 on a separate plots, and be sure to add add a
title and axis labels.
Note that the results will not be too interesting. To make a quality Moody diagram we
need more data points over a wider range of Re values, and we should use a log-scale. No
worries, we will build up to this in future chapters.
79
Chapter
4
Logic and vectors
In Chapter 4 we continue to build our foundational knowledge of MATLAB with
the introduction of logical statements and vectors. By the end of this chapter you
will be able to:
• Demonstrate the construction of logical blocks
• Recall how to dynamically create vectors in the context of for loops
• Be able to pre-define a static vector and assign values to its elements
• Apply the use of logical blocks and vectors to solve basic engineering problems
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
4.1 Checking preconditions
Some of the loops in the previous chapter don’t work if the value of n isn’t set
correctly before the loop runs. For example, this loop computes the sum of the
first n elements of a geometric sequence:
a1 = 1;
total = 0;
for i=1:n
a = a1 * 0.5^(i-1);
total = total + a;
end
ans = total
It works for any positive value of n, but what if n is negative? In that case, you get:
81
t Without knowing it, we
actually had this case in
Exercise 3.6 in your nested
loop to compute the factorial of 2*i+1. The inner
(nested) loop should be for
k=2:(2*i+1), where the
outer loop should be for
i=0:n, where n is the highest term in the sum. In the
first pass, when i=0, the inner loop will be for k=2:1.
MATLAB therefor did not
perform the loop. It worked
out correctly, because we
initialize our factorial variable with a value of 1, and 1!
is 1.
82
Chapter 4 Logic and vectors
total = 0
Why? Because the expression 1:-1 means “all the numbers from 1 to –1, counting
up by 1.” It’s not immediately obvious what that should mean, but MATLAB’s
interpretation is that there aren’t any numbers that fit that description, so the
result is
>> 1:-1
ans = Empty matrix: 1-by-0
If the matrix is empty, you might expect it to be “0-by-0,” but there you have it. In
any case, if you loop over an empty range, the loop never runs at all, which is why
in this example the value of total is zero for any negative value of n.
If you are sure that you will never make a mistake, and that the preconditions
of your functions will always be satisfied, then you don’t have to check. But for
the rest of us, it is dangerous to write a script, like this one, that quietly produces
the wrong answer (or at least a meaningless answer) if the input value is negative.
A better alternative is to use an if statement.
4.2 if
The if statement allows you to check for certain conditions and execute statements if the conditions are met. In the previous example, we could write:
t Remember, MATLAB’s documentation is your friend.
Try help if and doc if.
You can also do the same
for else and elseif.
t While I will not use them
in the current version of
the text, know that MATLAB does have an error
function that can be used
to throw an error and display a message, and also a
warning function that can
be used to display warning
messages.
if n<0
ans = NaN
end
The syntax is similar to a for loop. The first line specifies the condition we are
interested in; in this case we are asking if n is negative. If it is, MATLAB executes
the body of the statement, which is the indented sequence of statements between
the if and the end. MATLAB doesn’t require you to indent the body of an if
statement, but it makes your code more readable, so you should do it, and don’t
make me tell you again.
In this example, the “right” thing to do if n is negative is to set ans = NaN,
which is a standard way to indicate that the result is undefined (not a number).
If the condition is not satisfied, the statements in the body are not executed;
put differently, if the condition is not met, no action is taken and the program
proceeds. Sometimes there are alternative statements to execute when the condition is false. In that case you can extend the if statement with an else clause.
The complete version of the previous example might look like this:
4.2 if
if n<0
ans = NaN
else
a1 = 1;
total = 0;
for i=1:n
a = a1 * 0.5^(i-1);
total = total + a;
end
ans = total
end
Statements like if and for that contain other statements are called compound
statements. All compound statements end with, well, end. Take note of my use of
indentation. It is meant to make the code more readable.
In this example, one of the statements in the else clause is a for loop. Putting
one compound statement inside another is legal and common, and sometimes
called nesting.
To be completely clear as to what MATLAB is doing here, let’s walk through
the flow of calculations. First, MATLAB checks whether n is less than 0. If it is, the
if statement is true, and MATLAB sets ans = NaN and proceeds directly to the
end statement nicely lined up with if and else. If n is equal to or greater than 0,
the if statement is false, and MATLAB therefore proceeds to the else statement,
and performs the calculations between else and the final end.
Getting back to the use of indentation: if, else, and end make up a compound statement, and are all therefore aligned. We start with the if statement,
and if it is false, we jump to else. They are aligned to make this clear. The other
lines are indented to show they are part of this compound statement. With the
if statement, we “open” our (outer) compound statement. With the for, we
open a second, nested (inner) compound statement. We must “close” the for
loop (or the inner statement) before we close the (outer) if compound statement.
Therefore the first end statement ends (or closes) the for loop since it must be
closed first. To make this clear, we line them up.
I hope my long-winded discussion in the last two paragraphs has not confused
you more. As we begin to look at examples and you begin practicing, it will all
come together. Lastly, remember the else statement essentially means for all
other cases. You can add additional cases beside that in the original if statement.
This would be accomplished with an elseif statement, where you can have as
many elseif statements as you would like. An example of this might be:
83
84
Chapter 4 Logic and vectors
t Look carefully at the use of
elseif here. If MATLAB
gets to the elseif statement, it is because n ≥ 0. If
statement 2 is performed,
it is because n < 1 and
the elseif statement was
true. Putting it all together,
statement 2 is performed
if and only if 0 ≤ n < 1. If n is
an integer, it must be 0.
if n < 0
statement 1
elseif n < 1
statement 2
else
statement 3
end
If n is less than 0, execute statement 1 and proceed to end. If n is not less than
0, proceed to the elseif statement. Here we check if n is less than 1. If it is,
execute statement 2 and then proceed to end. If n is not less than 1, proceed to
else and evaluate statement 3. Please note that elseif is a single word; it is
tempting to add a space, but that would be wrong and is a common error I have
seen students make in the past.
4.3 Relational operators
t I often forget if 1 represents
“true” or “false”. This is a
perfect case of using the
command line to check
your understanding (i.e.,
unit testing). Try something
simple like 5<2 and/or 5>2.
What is the result?
t You must use caution
when checking for equivalence with “==”. Remember, we have seen
that MATLAB computes
sin(pi)=1.2246e-16.
Due to numerical limitations, it is not exactly 0.
Instead of using if x==0,
you might try instead if x
> n-tiny && x < n+tiny,
where “tiny” is a small
number which you deem
close enough to 0 for your
application, and “n” is the
value you are looking for.
There will certainly be times
when we will use “==”, such
as when we are only dealing
only integers.
The operators that compare values, like < and > are called relational operators
because they test the relationship between two values. The result of a relational
operator is one of the logical values: either 1, which represents “true,” or 0, which
represents “false.”
Relational operators often appear in if statements, but you can also evaluate
them at the prompt:
>> x = 5;
>> x < 10
ans = 1
You can assign a logical value to a variable:
>> flag = x > 10
flag = 0
A variable that contains a logical value is often called a flag because it flags the
status of some condition.
The other relational operators are <= and >=, which are self-explanatory, ==,
for “equal,” and ~=, for “not equal.” (In some logic notations, the tilde is the
symbol for “not.”)
Don’t forget that == is the operator that tests equality, and = is the assignment
operator. If you try to use = in an if statement, you get a syntax error:
4.4 Logical operators
>> if x=5
if x=5
|
Error: The expression to the left of the equals sign is not a
valid target for an assignment.
Did you mean:
>> x = 5
MATLAB thinks you are making an assignment to a variable named if x, and
suggests using just x since spaces are not allowed in variable names.
4.4 Logical operators
To test if a number falls in an interval, you might be tempted to write something
like 0 < x < 10, but that would be wrong, so very wrong. Unfortunately, in many
cases, you will get the right answer for the wrong reason. For example:
>> x = 5;
>> 0 < x < 10
ans = 1
% right for the wrong reason
But don’t be fooled!
>> x = 17
>> 0 < x < 10
ans = 1
% just plain wrong
The problem is that MATLAB is evaluating the operators from left to right, so
first it checks if 0<x. It is, so the result is 1. Then it compares the logical value 1
(not the value of x) to 10. Since 1<10, the result is true, even though x is not in the
interval. For beginning programmers, this is an evil, evil bug!
One way around this problem is to use a nested if statement to check the
two conditions separately:
ans = 0
if 0<x
if x<10
ans = 1
end
end
or you can be creative with an elseif statement:
85
86
Chapter 4 Logic and vectors
ans = 0
if 0>=x
% I will provide no statement which means
% do nothing if this is true, proceed to end.
elseif x<10
% We only get to this elseif statement if x>0,
% for which the first if statement is false.
% If we find x<10, this is the same as
% 0<x<10
ans = 1
end
But it is more concise to use the AND operator, &&, to combine the conditions.
>> x = 5;
>> 0<x && x<10
ans = 1
>> x = 17;
>> 0<x && x<10
ans = 0
The result of AND is true if both of the operands are true. The OR operator, ||, is
true if either or both of the operands are true. Put differently, the OR operator is
true is at least one of operands is true.
You can have as many AND and/or OR operators you would like on a single
line. Comparisons will be made from left to right, unless parentheses are added.
4.5 exist
Let’s return to section 4.1 Checking preconditions. Our discussion so far has
been limited to checking whether or not a variable (a pre-condition) is properly
defined. For example, if I am solving a problem using the Antoine equation, is the
specified temperature within the range of applicability. It may also be desirable
to check to see if a variable (required as a pre-condition) is defined at all. Let’s
P
look at a basic example. I wish to compute ni=1 (i + 1)2 , where n is the number of
terms to include in the sum; n is our required pre-condition. To accomplish this,
I write the following script
4.5 exist
87
Listing 4.1 sum_test.m
1
2
3
4
5
6
7
8
9
10
% Script to compute the sum of (i+1)^2 from 1 to n
% Precondition: n, the number of terms in the sum
% Post-condition: total, contains the value of the sum
total = 0
for i = 1:n
total = total+(i+1)^(2);
end
total
Let’s give it a try.
>> n = 5;
>> sum_test
total = 0
total = 90
Next, let’s clear the variable n and see what would happen if we forget to
define it.
>> clear n;
>> sum_test
total = 0
Undefined function or variable 'n'.
Error in sum_test (line 6)
for i = 1:n
The script returns the value of total = 0, which is the value we initialize it to,
and then also an error message indicating that n is undefined along with the line
on which the error was encountered. This is a good error message. However, if it
is missed by the user for one reason or another, they have total = 0 which may
be used in subsequent calculations.
You may therefore wish to check to see if n has been defined. If it has, run
the code as normal, if not, return an answer such as total = NaN and possibly
an error message of your own. This can be accomplished with the exist function. There are two ways to call it, I prefer the natural function form so that I
remember it is a function. When used to check for the existence of a variable, it
can be thought of as a logical function, returning a value of 1 for true, the variable
exists, and a value of 0 for false, the variable does not exist. It takes the basic form
exist(’name’,’var’), where name is the name of the variable you are checking
exists, and var indicates that we are checking for the existence of a variable. You
can check for the existence of things besides variables, but you can read the documentation for that. Let’s try and integrate exist into our script.
88
Chapter 4 Logic and vectors
Listing 4.2 sum_test_b.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% Script to compute the sum of (i+1)^2 from 1 to n
% Precondition: n, the number of terms in the sum
% Post-condition: total, contains the value of the sum
if exist('n','var')
total = 0;
for i = 1:n
total = total+(i+1)^(2);
end
total
else % n does not exist
disp('The pre-condition n is not defined')
total = NaN
end
Let’s give it a try
>> sum_test_b
The pre-condition n is not defined
total = NaN
Remember, another issue with scripts is they can read variables from and
save variables to your workspace. What would be unfortunate is if total was a
variable already defined in your workspace that you did not want to overwrite.
Exist could be used to check if total is already defined. If it is, then you could
save the current value to a new variable, or maybe you print a warning, or maybe
you prevent the script from running all together. The choice is up to you. The last
note I will make, if you need to check for the existence of more than one variable,
then you can combine exist with the AND operator, &&.
4.6 Vectors
The values we have seen so far are all single numbers, which are called scalars
to contrast them with vectors and matrices, which are collections of numbers.
In this chapter we will focus on the basics, in the context of loops. Later we will
revisit the topic in greater detail.
A vector in MATLAB is similar to a sequence in mathematics; it is a set of
numbers that correspond to positive integers. What we called a “range” in the
previous chapter was actually a vector.
In general, anything you can do with a scalar, you can also do with a vector.
You can assign a vector value to a variable:
4.7 Vector arithmetic
>> X = 1:5
X = 1
2
3
89
4
5
Variables that contain vectors are often capital letters. That’s just a convention;
MATLAB doesn’t require it, but for beginning programmers it is a useful way to
remember what is a scalar and what is a vector.
Just as with sequences, the numbers that make up the vector are called elements.
4.7 Vector arithmetic
You can perform arithmetic with vectors, too. If you add a scalar to a vector,
MATLAB increments each element of the vector:
>> Y = X+5
Y = 6
7
8
9
10
The result is a new vector; the original value of X is not changed.
If you add two vectors, MATLAB adds the corresponding elements of each
vector and creates a new vector that contains the sums:
>> Z = X+Y
Z = 7
9
11
13
15
But adding vectors only works if the operands are the same size. Otherwise:
>> W = 1:3
W = 1
2
3
>> X+W
Error using +
Matrix dimensions must agree.
The error message in this case is confusing, because we are thinking of these
values as vectors, not matrices. The problem is a slight mismatch between math
vocabulary and MATLAB vocabulary.
4.8 Everything is a matrix
In math (specifically in linear algebra) a vector is a one-dimensional sequence of
values and a matrix is two-dimensional (and, if you want to think of it that way, a
scalar is zero-dimensional). In MATLAB, everything is a matrix.
t This gives us a deeper understanding of how a for
loop works. MATLAB keeps
track of the loop iteration
number. Each iteration the
loop index variable is set
equal to that corresponding
element of the specified
vector.
90
Chapter 4 Logic and vectors
You can see this if you use the whos command to display the variables in the
workspace. whos is similar to who except that it also displays the size and type of
each variable.
First I’ll make one of each kind of value:
>> scalar = 5
scalar = 5
>> vector = 1:5
vector = 1
2
3
4
5
>> matrix = ones(2,3)
matrix =
1
1
1
1
1
1
t To get a vector of 1’s, try
ones(1,3). MATLAB can
also created a new matrix of
0’s, Try zeros(2,3). Both
are useful for creating a
new matrix of a given dimension and initializing it.
What is the result if you try
ones(3)? A 3 by 3 “square”
matrix. Have a look at the
documentation with the
help command.
ones is a function that builds a new matrix with the given number of rows (2) and
columns (3), and sets all the elements to 1. Now let’s see what we’ve got.
>> whos
Name
scalar
vector
matrix
Size
Bytes
1x1
1x5
2x3
8
40
32
Class
Attributes
double
double
double
According to MATLAB, everything is a double array: “double” is another name
for double-precision floating-point numbers, and “array” is another name for a
matrix.
The only difference is the size, which is specified by the number of rows and
columns. The thing we called scalar is, according to MATLAB, a matrix with one
row and one column. Our vector is really a matrix with one row and 5 columns.
And, of course, matrix is a matrix, with 2 rows and 3 columns.
The point of all this is that you can think of your values as scalars, vectors, and
matrices, and I think you should, as long as you remember that MATLAB thinks
everything is a matrix.
Here’s another example where the error message only makes sense if you
know what is happening under the hood:
>> X = 1:5
X = 1
2
3
4
5
>> Y = 1:5
Y = 1
2
3
4
5
4.9 Indices
91
>> Z = X*Y
Error using *
Inner matrix dimensions must agree.
First of all, * is the MATLAB function that performs matrix multiplication. The
reason the “inner matrix dimensions must agree” is the way matrix multiplication
is defined in linear algebra. Any scalar (a 1-by-1 matrix) may multiply anything.
Otherwise, the number of columns in X has to equal the number of rows in Y
(those are the inner dimensions).
If you don’t know linear algebra, this doesn’t make much sense. When you
saw X*Y you probably expected it to multiply each of the elements of X by the
corresponding element of Y and put the results into a new vector. That operation
is called elementwise multiplication, and the operator that performs it is .*:
>> X .* Y
ans = 1
4
9
16
25
We’ll get back to the elementwise operators later; you can forget about them for
now.
and elementwise exponentiation (.ˆ).
4.9 Indices
You can select elements of a vector with parentheses:
>> Y = 6:10
Y = 6
7
8
9
10
>> Y(1)
ans = 6
>> Y(5)
ans = 10
This means that the first element of Y is 6 and the fifth element is 10. The number
in parentheses is called the index because it indicates which element of the vector
you want.
The index can be any kind of expression.
>> i = 1;
>> Y(i+1)
ans = 7
t As we will see, we also have
elementwise division (./)
t So far we have only used the
ones command to create
a matrix, which is not too
interesting. But to select
an element of a matrix,
we would have something
like this: Y(3,4). The first
number is the row index,
and the second number is
the column index.
92
Chapter 4 Logic and vectors
Loops and vectors go together like the storm and rain. For example, this loop
displays the elements of Y.
for i=1:5
Y(i)
end
t length can also be used
with a matrix to find the
number of elements in a
dimension (along a row
or column). You can also
ask for the length of a subset of a vector or matrix
dimension. But more on
this later... or have a look at
the documentation if you
are eager. And if you are
really eager, when working
with matrices the related
function size can be very
useful.
Each time through the loop we use a different value of i as an index into Y.
A limitation of this example is that we had to know the number of elements in
Y. We can make it more general by using the length function, which returns the
number of elements in a vector:
for i=1:length(Y)
Y(i)
end
There. Now that will work for a vector of any length.
4.10 Indexing errors
An index can be any kind of expression, but the value of the expression has to be
a positive integer, and it has to be less than or equal to the length of the vector. If
it’s zero or negative, you get this:
>> Y(0)
Subscript indices must either be real positive integers or logicals.
“Subscript indices” is MATLAB’s longfangled way to say “indices.” “Real positive integers” means that complex numbers are out. And you can forget about “logicals”
for now; we will certainly come back to logicals later.
If the index is too big, you get this:
>> Y(6)
Index exceeds matrix dimensions.
There’s the “m” word again, but other than that, this message is pretty clear.
Finally, don’t forget that the index has to be an integer:
>> Y(1.5)
Subscript indices must either be real positive integers or logicals.
4.11 Vectors and sequences
93
4.11 Vectors and sequences
Vectors and sequences go together like ice cream and apple pie. For example,
another way to evaluate the Fibonacci sequence is by storing successive values
in a vector. Again, the definition of the Fibonacci sequence is F 1 = 1, F 2 = 1, and
F i = F i −1 + F i −2 for i ≥ 3. In MATLAB, that looks like:
Listing 4.3 fibonacci_seq_script.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
% Computing the Fibonacci sequence out to n
% Precondition: n, the number of terms in the sequence
% Postcondition: F, a vector for the Fibonacci sequence
%
where each element corresponds to the
%
term in the sequence. The final element
%
is stored to ans.
% Initializing the sequence
F(1) = 1;
F(2) = 1;
% Computing elements 3 to n
for i=3:n
F(i) = F(i-1) + F(i-2);
end
% Assigning the value of the nth element to ans
ans = F(n)
Notice that I am using a capital letter for the vector F and lower-case letters for the
scalars i and n. At the end, the script extracts the final element of F and stores it
in ans, since the result of this script is supposed to be the n th Fibonacci number,
not the whole sequence.
If you had any trouble with Example 3.7, you have to appreciate the simplicity
of this version. The MATLAB syntax is similar to the math notation, which makes
it easier to check correctness. The only drawbacks are
• You have to be careful with the range of the loop. In this version, the loop
runs from 3 to n, and each time we assign a value to the ith element. It
would also work to “shift” the index over by two, running the loop from 1 to
n-2:
t You will notice that within
the Editor window that the
nice folks at MATLAB caution you about assigning
a value to the variable ans.
Why? Because when you
perform a MATLAB operation and do not assign the
result to a variable, MATLAB will assign the result to
ans. So MATLAB is just reminding you so that you do
not accidentally overwrite
it.
94
Chapter 4 Logic and vectors
Listing 4.4 fibonacci_seq_shift_script.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
% Computing the Fibonacci sequence out to n
% Precondition: n, the number of terms in the sequence
% Postcondition: F, a vector for the Fibonacci sequence
%
where each element corresponds to the
%
term in the sequence. The final element
%
is stored to ans.
% Initializing the sequence
F(1) = 1;
F(2) = 1;
% Computing elements 3 to n
for i=1:n-2
F(i+2) = F(i+1) + F(i);
end
% Assigning the value of the nth element to ans
ans = F(n)
Either version is fine, but you have to choose one approach and be consistent. If you combine elements of both, you will get confused. I prefer the
version that has F(i) on the left side of the assignment, so that each time
through the loop it assigns the ith element.
• If you really only want the nth Fibonacci number, then storing the whole
sequence wastes some storage space. But if wasting space makes your code
easier to write and debug, that’s probably okay.
Example 4.1
Write a loop that computes the first n elements of the geometric sequence A i +1 =
A i /2 with A 1 = 1. Notice that the math notation puts A i +1 on the left side of the
equality. When you translate to MATLAB, you may want to shift the index. Note:
We are only asked to compute the terms of the series and not the sum as we did in
Example 3.4.
4.11 Vectors and sequences
Solution: (Link to screen cast and accompanying M-file.) I will provide two examples. First, I will shift the index as suggested. To me, this makes more sense
logically. Second, I will compute the terms using the notation as written.
Listing 4.5 geometic_series_elements.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
%
% Compute the first
% Precondition: n,
% Postcondition: at
%
we
%
n elements of the geometric seris A_i = A_{i-1}/2
the number of terms you wish to compute
the conclusion of the loop, the n elements will
stored to the vector A
% The first element is known and equal to 1
A(1) = 1;
% Computing elements 2 to n
for i=2:n
% I shifted the index by 1 and start my loop at 2.
A(i) = A(i-1)/2;
end
% Displaying all n elements. If n is large, you may wish to remove this.
A
Listing 4.6 geometic_series_elements_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
%
% Compute the first
% Precondition: the
% Postcondition: at
%
we
%
n elements of the geometric seris A_{i+1} = A_i/2
value of n, how many terms you wish to compute
the conclusion of the loop, the n elements will
stored to the vector A
% The first element is known and equal to 1
A(1) = 1;
% Computing elements 2 to n
for i=1:n-1
% Notice here my loop only goes up to n-1 since I am computing
% the term i+1.
A(i+1) = A(i)/2;
end
% Displaying all n elements. If n is large, you may wish to remove this.
A
95
96
Chapter 4 Logic and vectors
4.12 Sizing vectors
Notice in the examples of Section 4.11 and in Example 4.1 we did not need to
pre-define the size of the final vector. Our vectors were “dynamic”, their size
changed every iteration. Let’s use the command line to take a look at what is
going on:
>> T(1) = 1
T = 1
>> T(2) = 12
T = 1 12
>> T(3) = 5
T = 1 12 5
We start with a vector of length 1. Then each time we reference a subsequent
element, the length increases by 1. What would happen if we skipped ahead and
assign a value to element 8?
>> T(8) = 1
T = 1
12
5
0
0
0
0
1
The length of the vector is increased from 3 to 8, and the value of elements 4 to 7,
which we “skipped”, are assigned a value of 0.
Likewise, if we were to move next to the element in the 8th row and 12th column, all of the elements that we “skipped” will be assigned a value of zero:
>> T(8,12) = 1
T =
1
12
0
0
0
0
0
0
0
0
0
0
0
0
0
0
5
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
Having dynamic vectors and matrices is convenient and may simplify your code.
However, you may find that the MATLAB editor suggests that you pre-allocate
your vectors and matrices when possible. The issue is that every time you re-size
your vector or matrix, MATLAB needs to reassign memory. This can be a time
consuming operation. For the purpose of this class, you could safely ignore the
suggestion without any implications. However, if you know the final size of your
4.13 Creating vectors
97
matrix or length of your vector, a better practice would be to initialize (or create)
the matrix or vector to be the final size using zeros or ones. This way memory
never needs to be reassigned. For example, if we knew the final length of vector G
would be 10, we might initially define G as:
>> G = zeros(1,10)
G = 0
0
0
0
0
0
0
0
0
0
For this class, you will not notice a change in efficiency. However, if you know
the final size of your matrix or length of your vector, then initializing your matrix
or vector to be the final size is the better practice. If you are interested in high
performance computing, then getting in the habit of sizing your vectors and
matrices up-front is a good idea. For these applications any time savings can have
a huge impact.
4.13 Creating vectors
Before moving on, I would like to pause, summarize, and go through some examples of creating vectors. Some commands we will use in this chapter, while some
(most) will find use later on. But we will see them here, then get in more depth
later.
We first saw in the context of for loops that we could use the colon operator.
The command begin:end will create a vector that goes from begin to end in increments of 1. The values of begin and end can be anything. But remember that
the increment will be 1. Let’s consider a few examples and see why I emphasize
this point.
>> 1:5
ans =
1
>> 1.1:5.8
ans =
1.1000
2
3
2.1000
4
5
3.1000
4.1000
5.1000
>> 5:1
ans =
1x0 empty double row vector
Beside the colon operator, we have the extended colon operator. The extended
colon operator takes the form begin:increment:end, where here we can specify
an increment value (beside the default value of 1).
t Row and column vectors
will be a topic of conversation later. For now, we will
only consider row vectors.
98
Chapter 4 Logic and vectors
>> 1:2:6
ans =
1
3
>> 1:1.5:6
ans =
1.0000
>> 6:-1:1
ans =
6
5
2.5000
5
4
4.0000
3
2
5.5000
1
We see that we can go backwards now too.
We have seen the ones and zeros command, where here we specify we have
only a single row to create a vector.
>> ones(1,5)
ans =
1
1
1
1
1
>> zeros(1,5)
ans =
0
0
0
0
0
We have defined vectors dynamically:
>> T(1) = 5
T =
5
>> T(2) = 3
T =
5
3
>> T(3) = 10
T =
5
3
10
If our goal was to create a vector with data that we know, we can accomplish
this in a single command using brackets, with the elements separated either by a
space or a comma.
4.13 Creating vectors
>> R = [5 3 0]
R =
5
3
99
0
>> S = [5, 3, 0]
S =
5
3
0
For the stubborn Excel user reluctant to give MATLAB a try, you could likewise
accomplish this graphically using the New Variable button on the Home tab. You
Figure 4.1 The New Variable and
can likewise edit an existing variable using the Open Variable button, or just douOpen Variable buttons located on
ble click the variable name in the Workspace . To create a new variable, simply
the Home tab.
click New Variable , then type into a spreadsheet-like environment. To give the
variable a name, click on the tab that currently says “unnamed”, and type in a
variable name of your choice. This is best demonstrated in the following screen
cast.
Another useful command is linspace. I often find students using the function in class without ever mentioning it. The basic command is linspace(begin,end)
which will create a vector of 100 equally spaced points between begin and end. If
you want to use more or less than 100 points, you can use linspace(begin,end,n)
where n is the number of points. This can be very useful for the examples where
we have looped over a range of temperatures. Here the endpoints need not be integers. If we were to create a vector of temperatures using linspace and assigned
it to variable T, we could then loop as for i=1:length(T), and each iteration
point to the i th element of T.
>> X = linspace(2.5, 12, 6)
X =
2.5000
4.4000
6.3000
8.2000
10.1000
12.0000
And very last, we will end with the command rand. The basic form of the
command is rand(1,n), which will create a vector of length n of uniformly distributed pseudorandom numbers between 0 an 1. Technically the numbers are
pseudorandom as they are generated using a definite mathematical procedure,
but for our purposes you can think of them as random. It takes the same form as
ones and zeros, where the first argument is actually the number of rows. This
will get much more attention later.
>> Y=rand(1,3)
Y =
0.8147
0.9058
0.1270
Again, we will not use most of these commands for some time yet. However,
there is always a curiosity when we begin to discuss vectors, so here it is. When
100
Chapter 4 Logic and vectors
we use the command in the text, we will give it a more proper introduction.
But hopefully this satisfies your curiosity. Now back to our originally scheduled
programming.
4.14 Plotting vectors
Plotting and vectors go together like the moon and June, whatever that means.
If you call plot with a single vector as an argument, MATLAB plots the indices
on the x-axis and the elements on the y-axis. To plot the Fibonacci numbers we
computed in Section 4.11:
>> n = 10;
>> fibonacci_seq_script
ans = 55
>> plot(F)
>> xlabel('n')
>> ylabel('F_n')
60
50
Fn
40
30
20
10
0
1
2
3
4
5
6
7
8
9
10
n
Figure 4.2 A plot of the first ten elements of the Fibonacci sequence.
This display is often useful for debugging, especially if your vectors are big enough
that displaying the elements on the screen is unwieldy.
If you call plot with two vectors as arguments, MATLAB plots the second one
as a function of the first; that is, it treats the first vector as a sequence of x values
and the second as corresponding y value and plots a sequence of (x, y) points.
4.14 Plotting vectors
>>
>>
>>
>>
>>
101
X = 1:5;
Y = 6:10;
plot(X, Y)
xlabel('X')
ylabel('Y')
10
9.5
9
Y
8.5
8
7.5
7
6.5
6
1
1.5
2
2.5
3
3.5
4
4.5
5
X
Figure 4.3 An example of plotting two vectors, one against the other.
By default, MATLAB draws a blue line, but you can override that setting with the
same kind of string we saw in Section 3.2. For example, the string ’ro-’ tells
MATLAB to plot a red circle at each data point; the hyphen means the points
should be connected with a solid line. Rather than a solid line, we can obtain a
dashed line with ’--’ and a dotted line with ’:’.
In this example, I stuck with the convention of naming the first argument X
(since it is plotted on the x-axis) and the second Y. There is nothing special about
these names; you could just as well plot X as a function of Y. MATLAB always treats
the first vector as the “independent” variable, and the second as the “dependent”
variable (if those terms are familiar to you).
I should also point out that despite plotting multiple data points, five in this
case, I did not need to use the hold on command. The reason is that MATLAB
treats this a single set. If we wanted to add any additional points to the plot using
a subsequent plot command, then we would need to use hold on. Of course I
could have just used hold on from the start and nothing would have changed.
t When overriding the default
settings and specify the line
and/or symbol type and
the color, MATLAB does not
care about the order. Try for
example ’ro-’, ’r-o’, and
’-or’. You should find they
all yield the same result.
102
Chapter 4 Logic and vectors
4.15 Reduce
A frequent use of loops is to run through the elements of an array and add them
up, or multiply them together, or compute the sum of their squares, etc. This kind
of operation is called reduce, because it reduces a vector with multiple elements
down to a single scalar.
For example, this loop adds up the elements of a vector named X (which we
assume has been defined).
total = 0
for i=1:length(X)
total = total + X(i)
end
ans = total
The use of total as an accumulator is similar to what we saw in Section 3.4. Again,
we use the length function to find the upper bound of the range, so this loop will
work regardless of the length of X. Each time through the loop, we add in the ith
element of X, so at the end of the loop total contains the sum of the elements.
This is additionally an excellent example to practice with because MATLAB has
a built-in function sum that does the same thing. You could use it to check the
script for correctness as sum(X).
Example 4.2
Write a similar loop that multiplies all the elements of a vector together. You might
want to call the accumulator total_prod, and you might want to think about the
initial value you give it before the loop.
Solution: (Link to screen cast and accompanying M-file.) Careful here. Do not
initialize your accumulator variable to a value of zero. Anything times zero is
zero. This is fine when performing a summation, but not a continuous product.
Initialize it to a value of one.
4.16 Apply
103
Listing 4.7 vector_continuous_product.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% Multiply all of the elements of a vector X together
%
% Precondition: X, vector containing the sequence of elements
% Postcondition: total_prod, the result of multiplying all of the elements
%
of the vector X together
%
% Initializing the accumulator variable
total_prod = 1;
% Computing the continuous product
for i=1:length(X)
total_prod = total_prod*X(i);
end
% Displaying the final result
total_prod
The calculation corresponds to computing a continuous product. Like computing
the sum, the continuous product is a common operation. And as you might expect,
MATLAB has a built-in function for computing the continuous product that you
can use to check the correctness of your code. The function is prod, and you would
use it here as prod(X).
4.16 Apply
Another common use of a loop is to run through the elements of a vector,
perform some operation on the elements, and create a new vector with the results.
This kind of operation is called apply, because you apply the operation to each
element in the vector.
For example, the following loop computes a vector Y that contains the squares
of the elements of X (assuming, again, that X is already defined).
for i=1:length(X)
Y(i) = X(i)^2
end
Example 4.3
Write a loop that computes a vector Y that contains the sines of the elements of X.
To test your loop, write a script that
(a) Uses linspace to assign to X a vector with 100 elements running from 0 to
2π (X = linspace(0,2*pi)).
t Remember that X(i) refers
to a single element of the
vector X. We can therefore
use ˆ and do not need .ˆ,
although both would give
the same result.
t Later on when we discuss
elementwise operations,
you will find we could just
instead do Y = X.ˆ2 and
get the same result. Further,
functions like sin operate
elementwise. So in the
next example, we could
replace the entire loop with
Y=sin(X) and get the same
result.
104
Chapter 4 Logic and vectors
(b) Uses your loop to store the sines in Y.
(c) Plots the elements of Y as a function of the elements of X.
Solution: (Link to screen cast and accompanying M-file.)
Let’s put all of the parts in a single script. For more information on how to use
linspace, have a look at MATLAB’s documentation (doc or help).
Listing 4.8 sine_plot.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
% Plot sine over the range 0 to 2*pi
%
% Precondition: None
% Postcondition: Vector X will contain 100 elements from 0 to 2*pi,
%
vector Y will contain the corresponding value of sine,
%
and a plot of X vs Y will be produced
%
% Vector X containing angles from 0 to 2*pi
X = linspace(0,2*pi);
% Pre-size our vector Y that will contain the sine of X
Y = zeros(1,length(X));
for i=1:length(X)
Y(i) = sin(X(i));
end
% Plotting
clf; % clear figure, just in case
plot(X,Y)
xlabel('\theta [radians]')
ylabel('sin(\theta)')
xlim([0,2*pi])
% Finally, saving and printing the figure
savefig('sine_plot.fig')
print('-depsc','sine_plot.eps')
4.17 Search
Yet another use of loops is to search the elements of a vector and return the index
of the value you are looking for (or the first value that has a particular property).
For example, if a vector contains the computed altitude of a falling object, you
might want to know the index where the object touches down (assuming that the
ground is at altitude 0).
To create some fake data, we’ll use an extended version of the colon operator:
4.17 Search
105
X = 10:-1:-10
The values in this range run from 10 to –10, with a step size of -1. The step size is
the interval between elements of the range.
The following loop finds the index of the element 0 in X:
for i=1:length(X)
if X(i) == 0
ans = i
end
end
One funny thing about this loop is that it keeps going after it finds what it is
looking for. That might be what you want; if the target value appears more than
once, this loop provides the index of the last one.
But if you want the index of the first one (or you know that there is only one),
you can save some unnecessary looping by using the break statement.
for i=1:length(X)
if X(i) == 0
ans = i
break
end
end
break does pretty much what it sounds like. It ends the loop and proceeds
immediately to the next statement after the loop (in this case, there isn’t one, so
the script ends).
This example demonstrates the basic idea of a search, but it also demonstrates
a dangerous use of the if statement. Remember that floating-point values are
often only approximately right. That means that if you look for a perfect match,
you might not find it. For example, try this:
X = linspace(1,2)
for i=1:length(X)
Y(i) = sin(X(i))
end
plot(X, Y)
You can see in the plot that the value of sin (x) goes through 0.9 in this range, but
if you search for the index where Y(i) == 0.9, you will come up empty.
t Try writing a for loop with
the extended version of the
colon operator. This gives
you the ability to write a
for loop that increments
by a value other than 1 each
iteration.
106
Chapter 4 Logic and vectors
for i=1:length(Y)
if Y(i) == 0.9
ans = i
break
end
end
The condition is never true, so the body of the if statement is never executed.
Even though the plot shows a continuous line, don’t forget that X and Y are
sequences of discrete (and usually approximate) values. As a rule, you should
(almost) never use the == operator to compare floating-point values. There are a
number of ways to get around this limitation; we will get to them later.
Example 4.4
Write a loop that finds the index of the first negative number in a vector and stores
it in ans. If there are no negative numbers, it should set ans to –1 (which is not a
legal index, so it is a good way to indicate the special case).
t Instead of assigning –1 to
ans if there are no negative
numbers, 0 would be another excellent option. Not
only is 0 not a valid element
index, but in the context of
logicals, 0 means false (or
not true).
Solution: (Link to screen cast with accompanying M-file.) Let’s start by creating
an array of fake data.
>> X = 6:-1:-4;
The first negative number is –1 and it is the 8th element of the vector X.
Listing 4.9 negative_element.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
% Find the index of the first negative number in vector X
%
% Precondition: vector X
% Postcondition: ans will contain the index of the first negative number
%
of vector X
% Initialize ans with value of -1, so if there is no negative number
% it will return this value.
ans = -1;
for i=1:length(X)
if X(i) < 0
ans = i
break
end
end
>> negative_element
ans = 8
4.18 Spoiling the fun
4.18 Spoiling the fun
Experienced MATLAB programmers would never write the kind of loops in this
chapter, because MATLAB provides simpler and faster ways to perform many
reduce, filter and search operations. For example, the sum function computes the
sum of the elements in a vector and prod computes the product.
Many apply operations can be done with elementwise operators. The following statement is more concise than the loop in Section 4.16
Y = X .^ 2
Also, most built-in MATLAB functions work with vectors:
X = linspace(0, 2*pi)
Y = sin(X)
plot(X, Y)
Finally, the find function can perform search operations, but understanding it
requires a couple of concepts we haven’t got to, so for now you are better off on
your own. We will take a look at it later.
I started with simple loops because I wanted to demonstrate the basic concepts and give you a chance to practice. At some point you will probably have
to write a loop for which there is no MATLAB shortcut, and you have to work
your way up from somewhere. If you understand loops and you are comfortable
with the shortcuts, feel free to use them! Otherwise, you can always write out the
loop. Note, if you are interested in high performance computing applications,
vector operations with MATLAB are significantly more efficient than the use of
for loops.
Example 4.5
Write an expression that computes the sum of the squares of the elements of a
vector.
Solution: Assume we have a vector X. Based on what you just read you might try:
>> X = 1:10;
>> X2 = X.^2;
>> sum_square = sum(X2)
sum_square = 385
But if we search for “sum squared,” you will find that MATLAB can do even better.
>> sum_square = sumsqr(X)
sum_square = 385
107
108
Chapter 4 Logic and vectors
4.19 Strings
The chapter is titled “Logic and vectors”. Now that we have discussed them both,
I would like to take a minute to discuss strings. In you code you may find it
desirable to compare strings in a logical statement. You can do this, but you need
to be very careful. When you create a string, MATLAB saves the string as a vector,
with the index going 1 to the number of letters, from left to right.
>> test = 'Hello';
>> whos
whos
Name
Size
test
1x5
Bytes
10
Class
char
Attributes
test is a vector of length 5.
>> test
test = Hello
>> test(1)
ans = H
>> test(4)
ans = l
Later we will discuss the use of applying logical operators on vectors. But
two cautionary notes. When comparing strings: 1) MATLAB will compare each
element for equivalence and return a vector of 1’s and 0’s, 2) MATLAB is case
sensitive. You will notice that I tend to avoid logical comparisons with strings, but
there are always students in class that immediately want to use strings in logical
comparisons, so I mention this here.
4.20 Examples
Example 4.6
The ratio of consecutive Fibonacci numbers, F n+1 /F n , converges to a constant
value as n increases. Write a script that computes a vector with the first n elements
of a Fibonacci sequence (assuming that the variable n is defined), and then computes a new vector that contains the ratios of consecutive Fibonacci numbers. Plot
this vector to see if it seems to converge. What value does it converge on?
(Running, the vector seems to converge on a value of 1.6180.)
4.20 Examples
109
Solution: (Link to screen cast with accompanying M-file.)
Listing 4.10 fib_ratio.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
%
%
%
%
%
%
%
%
%
%
%
Computing the nth Fibonacci number and the ratio of
consecutive numbers
Pre-condition: n, the number of terms in the Fibonacci
series
Post-condition: Vector F will contain the Fibonacci
number of each each element index,
and F_ratio will contain the ratio of
consecutive Fibonacci numbers F(i+1)/F(i),
beginning with i = 2. We will also plot
F_ratio to look for convergence.
% Since we know the final size of F and F_ratio, let's presize the vectors
F = zeros(1,n);
F_ratio = zeros(1,n);
% Initializing
F(1) = 1;
F(2) = 1;
F_ratio(1) = 0;
F_ratio(2) = F(2)/F(1);
for i=3:n
F(i) = F(i-1)+F(i-2);
F_ratio(i) = F(i)/F(i-1);
end
% Clear our figure
clf
% Plot the Fibonacci ratio
plot(F_ratio,'ro-')
ylabel('Fibonacci ratio')
xlabel('i')
% Saving and printing the figure
savefig('fib_ratio.fig')
print('-depsc','fib_ratio.eps')
110
Chapter 4 Logic and vectors
Example 4.7
A certain famous system of differential equations can be approximated by a system
of difference equations that looks like this:
x i +1
=
y i +1
=
z i +1
=
¡
¢
xi + σ y i − xi d t
£
¤
y i + x i (r − z i ) − y i d t
¡
¢
z i + x i y i − bz i d t
(4.1)
(4.2)
(4.3)
• Write a script that computes the first 10 elements of the sequences X , Y and
Z and stores them in vectors named X, Y and Z.
Use the initial values X 1 = 1, Y1 = 2 and Z1 = 3, with values σ = 10, b = 8/3
and r = 28, and with time step d t = 0.01.
• Read the documentation for plot3 and comet3 and plot the results in 3
dimensions.
• Once the code is working, use semi-colons to suppress the output and then
run the program with sequence length 100, 1000 and 10000.
• Run the program again with different starting conditions. What effect does it
have on the result?
• Run the program with different values for σ, b and r and see if you can get a
sense of how each variable affects the system.
4.20 Examples
111
Solution:
Listing 4.11 diffe_sequence.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
%
%
%
%
%
%
%
Solving the certain famous system of differential equations using the
equations and conditions provided in the problem.
Pre-condition: None. I will use provided parameters/settings
Post-condition: 3-dimensional plots of the results as a function of
time-step. The numerical results will be stored to the
vectors X, Y, and Z.
% Clearing the variables just in case, since X, Y, and Z, are popular
% choices, or in case you just ran the script with a different number of
% time-steps.
clear X Y Z;
% The number of timesteps
n = 10000;
%
X
Y
Z
Since we know the final size of vectors X, Y and Z, let's pre-size them
= zeros(1,n);
= zeros(1,n);
= zeros(1,n);
% Initial values
X(1) = 1;
Y(1) = 2;
Z(1) = 3;
% Parameters
sigma = 10;
b = 8/3;
r = 28;
% Time-step
dt = 0.01;
for i = 1:(n-1)
X(i+1) = X(i)+sigma*(Y(i)-X(i))*dt;
Y(i+1) = Y(i)+(X(i)*(r-Z(i))-Y(i))*dt;
Z(i+1) = Z(i)+(X(i)*Y(i)-b*Z(i))*dt;
end
% For the first part of the problem we are asked to dispaly the values
% when n <= 10
if n <= 10
disp(X)
disp(Y)
disp(Z)
end
% Only generate 1 of the plots. Uncomment 1 and comment the other
% to switch
%plot3(X,Y,Z)
comet3(X,Y,Z)
112
Chapter 4 Logic and vectors
Example 4.8
The logistic map is often cited as an example of how complex, chaotic behavior
can arise from simple non-linear dynamical equations. It was popularized in a
seminal 1976 paper by the biologist Robert May. 1
It has been used to model the biomass of a species in the presence of limiting
factors such as food supply and disease. In this case, there are two processes at
work: (1) A reproductive process increases the biomass of the species in proportion
to the current population. (2) A starvation process causes the biomass to decrease
at a rate proportional to the carrying capacity of the environment less the current
population.
Mathematically this can be written as
X i +1 = r X i (1 − X i )
where X i is a number between zero and one that represents the biomass at year i ,
and r is a positive number that represents a combined rate for reproduction and
starvation.
• Write a script named logmap that computes the first 50 elements of X with
r=3.9 and X1=0.5, where r is the parameter of the logistic map and X1 is
the initial population.
• Plot the results for a range of values of r from 2.4 to 4.0. How does the
behavior of the system change as you vary r .
• One way to characterize the effect of r is to make a plot with r on the x-axis
and biomass on the y axis, and to show, for each value of r , the values of
biomass that occur in steady state. See if you can figure out how to generate
this plot.
1 This was adopted from the “Logistic map” Wikipedia page, which you can see for additional
details.
4.20 Examples
113
Solution:
Listing 4.12 logmap_1.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
%
%
%
%
%
%
%
%
The logistic map. Note that I will save my files as
logmap_*, where * will be 1, 2, or 3 for the first, second, and
third bullet respectively.
Pre-condition: None. I will hard-code everything
Post-condition: Vector X will have the first 50 elements of the series.
That is, it will contain the population for years 1 to
50, corresponding to the index number.
% Clearing the variable just in case since X is a
% also a good idea in case you previously ran the
% value of n. I'm not sure if I have mentioned it
% clear is another one of those commands where we
% supress output... there isn't any to supress.
clear X
popular choice. This is
script with a different
before, but note that
do not need a ; to
n = 50;
% Since we know the final size of X, let's pre-size the vector
X = zeros(1,n);
% Initial population
X(1) = 0.5;
% Combined rate for reproduction and starvation
r = 3.9;
for i=1:(n-1)
X(i+1)=r*X(i)*(1-X(i));
end
% Plotting the result
plot(X)
xlabel('i')
xlim([1,n])
ylabel('X')
% Saving and printing the figure
savefig('logmap_1.fig')
print('-depsc','logmap_1.eps')
114
Chapter 4 Logic and vectors
Listing 4.13 logmap_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
%
%
%
%
%
%
%
%
%
%
The logistic map. Note that I will save my files as
logmap_*, where * will be 1, 2, or 3 for the first, second, and
third bullet respectively.
Pre-condition: None. I will hard-code everything
Post-condition: Vector X will have the first 50 elements of the series.
That is, it will contain the population for years 1 to
50, corresponding to the index number. In this case, it
will be the last value of r we consider in the vector.
More imporant will be our plot of X for values of r
% Clearing the variable just in case since X is a popular choice. This is
% also a good idea in case you previously ran the script with a different
% value of n.
clear X
% Initial population
X(1) = 0.5;
%
%
%
%
R
Combined rate for reproduction and starvation
For part b, let's make r a vector of values from 2.4 to 4 in increments
of 0.2 using the extended colon operator. Note I will use R instead of r
because now it is a vector.
= 2.4:0.2:4.0;
n = 50;
% Clear our figure
clf
% Don't erase points each time we call plot
hold on
% Looping over all values of r, then all years of interest.
% For each value of r, we will plot the result before moving on to the next
% value. Notice that in my plot command I do not specify the color. This
% way MATLAB will automatically change the color for each new set.
for j = 1:length(R)
for i=1:(n-1)
X(i+1)=R(j)*X(i)*(1-X(i));
end
plot(X)
end
% Now let's make the plot look pretty and save it too.
xlabel('i')
xlim([1,n])
ylabel('X')
% Saving and printing the figure
savefig('logmap_2.fig')
print('-depsc','logmap_2.eps')
4.20 Examples
115
Note that when plotting the result for multiple values of r , it might be nice to add
a legend. I did not do so here as the plot was already very busy. However, doing
so is straightforward and I will demonstrate it for you as an example. First, this is
greatly facilitated by the command num2str which takes an argument of a number,
and converts it to a string. Second, we update the plot command to name each
data set as it is plotted. This is accomplished with the addition of ’DisplayName’
followed by the string you would like to use to label the data set. Third, since we
have already attached names to the data sets, we need just tell MATLAB to display
the legend with the command legend. Have a look at my updated code below.
Also, this is a very nice example of how to efficiently add legends to plots with
multiple data sets, that is particularly useful with for loops. Know that I had never
did this before this example. How do you think I figured it out? If you said I looked
in the MATLAB documentation, then you are correct. Have a look at the documentation page appropriately titled: “Add Legend to Graph.” It contains a section with
an example to “Create Simple Legend,” which is what we discussed previously,
followed by a a section with an example to “Specify Labels Using DisplayName.”
While not used here, I will also point out that for a long legend like ours, you could
look at the section “Legend with Multiple Columns” to learn how to to create a
legend with multiple columns. So the moral of the story is, if there is anything you
ever wished you could do with MATLAB, search the documentation.
Listing 4.14 logmap_2b.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
%
%
%
%
%
%
%
%
%
%
%
%
%
%
The logistic map. Note that I will save my files as
logmap_*, where * will be 1, 2, or 3 for the first, second, and
third bullet respectively.
This version has been added to demonstrate the use of num2str to add a
legend.
Pre-condition: None. I will hard-code everything
Post-condition: Vector X will have the first 50 elements of the series.
That is, it will contain the population for years 1 to
50, corresponding to the index number. In this case, it
will be the last value of r we consider in the vector.
More imporant will be our plot of X for values of r
% Clearing the variable just in case since X is a popular choice. This is
% also a good idea in case you previously ran the script with a different
% value of n.
clear X
% Initial population
X(1) = 0.5;
%
%
%
%
R
Combined rate for reproduction and starvation
For part b, let's make r a vector of values from 2.4 to 4 in increments
of 0.2 using the extended colon operator. Note I will use R instead of r
because now it is a vector.
= 2.4:0.2:4.0;
n = 50;
116
Chapter 4 Logic and vectors
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
% Clear our figure
clf
% Don't erase points each time we call plot
hold on
% Looping over all values of r, then all years of interest.
% For each value of r, we will plot the result before moving on to the next
% value. Notice that in my plot command I do not specify the color. This
% way MATLAB will automatically change the color for each new set.
%
% Notice my updated plot call. In this updated version we are naming the
% data set. Or you can essentially think of it as we are attaching the
% legend label to the set as we plot it.
for j = 1:length(R)
for i=1:(n-1)
X(i+1)=R(j)*X(i)*(1-X(i));
end
plot(X,'DisplayName',num2str(R(j)))
end
% Now let's make the plot look pretty and save it too.
xlabel('i')
xlim([1,n])
ylabel('X')
% We alreay attached the legend names to each data set. Now we just need to
% tell MATLAB to display the legend.
legend
% Saving and printing the figure
savefig('logmap_2b.fig')
print('-depsc','logmap_2b.eps')
4.20 Examples
117
Listing 4.15 logmap_3.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
%
%
%
%
%
%
The logistic map. Note that I will save my files as
logmap_*, where * will be 1, 2, or 3 for the first, second, and
third bullet respectively.
Pre-condition: None. I will hard-code everything
Post-condition: Plot of steady state X versus r
% Clearing the variable just in case since X is a popular choice
clear X;
% Initial population
X(1) = 0.5;
% Combined rate for reproduction and starvation
% From part b, we found that for r = 4, the steady state population was 0.
% Then decreasing in increments of 0.1, the result is chaotic until we get to
% 2.9. Then the population quickly converges to a steady state value.
R = 0:0.01:2.99;
n = 50000;
% clear our figure
clf
% don't erase points each time we call plot
hold on
% Looping over all values of r, then all years of interest
for j = 1:length(R)
for i=1:(n-1)
X(i+1)=R(j)*X(i)*(1-X(i));
end
plot(R(j),X(n),'ro')
end
% Now let's make the plot look pretty and save it too.
xlabel('i')
ylabel('X')
% I will set the x-axis range to go from the value of the first to last
% element of R. The first element is R(1). But what about the last. Well,
% the current value of j is that of the last loop iteration, which was
% equal to length(R). So we could use j or length(R) for the index. Or as
% we will see in the future, we could use end.
xlim([R(1),R(j)])
% Saving and printing the figure
savefig('logmap_3.fig')
print('-depsc','logmap_3.eps')
118
Chapter 4 Logic and vectors
4.21 CPB Examples
Example 4.9
We have worked with the Antoine equation for ethanol in several problems now.
Here we will continue the fun. Let’s revisit Example 1.5, 2.4, and 3.9. When working
with the Antoine equation, it is important to take note of the specified temperature
range over which use of the expression is recommended. While sometimes you
have no choice but to extrapolate your Antoine equation beyond this range, it
certainly is not a best practice.
a) Update your script from Example 2.4 part a) to compute vapor pressure to
check that the specified temperature is within the range of applicability. If the
temperature is outside the range, how might you communicate this to the user?
b) Update your script from Example 2.4 part c) to compute saturation temperature
to check that the specified pressure is within the range of applicability. If the
pressure is outside the range, how might you communicate this to the user?
c) Modify your script from Example 3.9 so that in the loop it stores the value
of T and log10 p sat to a vector. Then once the loop is complete, create your
Clapeyron plot using your vectors. Compare to your graph from Example 3.9.
Note that when plotting vectors, MATLAB plots all of the points at once. For
this case you therefore no longer need hold on. Represent your data as a line
with a circle for each data point. Label your axis and give your plot a title.
Solution: For this exercise we are updating solutions to previous problems with
some of the new tricks we have learned in this chapter. Namely in (a) and (b) we
will add logical checks so that the calculation is only performed under certain conditions; here the logical check will be to use the Antoine equation only if it is within
the range of applicability. If the use of Antoine equation is not appropriate, we will
display a message and also store NaN to the postcondition (solution) variables. We
use NaN because it will be propagated through subsequent calculations, so that
the end result is NaN. In (a) we are updating Listing 2.5 and in (b) we are updating
Listing 2.6.
In (c) we will use a vector to store the results of each loop iteration, and will then
apply plot to vectors. Remember that vector indices start at 1. So if were were to
loop over temperature in degrees C, if the loop index starts at 0 we will need to shift
up by 1 to refer to the appropriate vector index. In (c) we are updating Listing 3.12.
4.21 CPB Examples
119
Listing 4.16 Example_4_9a.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
%
%
%
%
%
%
%
%
%
%
%
%
%
Calculating the vapor pressure of ethanol using Antione's equation
Dr. Paluch, Example 4.9a
Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
oC) stored to variables a, b, and c, and the temperature
in K stored to variable t_K. Please also store Tmin and
Tmax, the minimum and maximum temperature, in K, to
variables t_K_min and t_K_max.
Post-condition: Psat in units of mmHg (p_mmHg), atm (p_atm),
kPa (p_kPa), and bar (psat_bar), and temperature in
degrees C (t_C)
% Check that we are in the range of applicability
if t_K >= t_K_min && t_K <= t_K_max
% First, let's convert T from oC to K
t_C = t_K-273.15
% Calcutating logPsat, with Psat in mmHg
log_p_mmHg = a-b/(t_C+c);
% Psat in mmHg
p_mmHg = 10^log_p_mmHg
% Psat in atm
p_atm = p_mmHg/760
% Psat in kPa
p_kPa = p_atm*101.325
% Psat in bar
p_bar = p_kPa/100
% Else, use is no appropriate
else
disp('Your input temperature is outside the range of use.')
p_mmHg = NaN;
p_atm = NaN;
p_kPa = NaN;
p_bar = NaN;
end
120
Chapter 4 Logic and vectors
Listing 4.17 Example_4_9b.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
% At a given pressure, calculate the correspnoding boiling point
% (or saturation temperature) using Antione's equation.
%
% Dr. Paluch, Example 4.9b
%
% Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
%
oC) stored to variables a, b, and c, and the pressure
%
in units of atm stored to p_atm. Please also store Tmin and
%
Tmax, the minimum and maximum temperature, in K, to
%
t_K_min and t_K_max
% Post-condition: The saturation temperature in units of oC (t_C) and K
%
(t_K)
%
% Convert the inputed pressure from atm to mmHg
p_mmHg = p_atm*760;
% Calculating the saturation temperature
t_C = b/(a-log10(p_mmHg))-c;
t_K = t_C + 273.15;
% Check that we are within the range of applicability
if t_K >= t_K_min && t_K <= t_K_max
% Note display is similar to disp, only it displays the variabale name too.
display(t_C)
display(t_K)
% Else, use is no appropriate
else
disp('Your input pressure is outside the range of use.')
t_C = NaN;
t_K = NaN;
end
4.21 CPB Examples
121
Listing 4.18 Example_4_9c.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
%
%
%
%
%
%
%
%
%
%
Plotting the vapor pressure of ethanol using Antione's equation
from 0 to 100 oC
Dr. Paluch, Example 4.9c
Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
oC) stored to variables a, b, and c.
Post-condition: Plot of log10 psat vs 1/T where T is in units of K
and psat is in units of mmHg
% Start by clearing our figure, just in case
clf
% Clear/delete our vectors in case we previously ran with a different,
% larger temperature range
clear log_p_mmHg tinv t_C
% We need to calculated log psat over the range 0 to 100 C
for t_C = 0:100
% Calcutating logPsat, with Psat in mmHg
% Remember loop indices start at 1, so we need to shift up by 1
log_p_mmHg(t_C+1) = a-b/(t_C+c);
% Calculating 1/T with T in units of K
tinv(t_C+1) = 1/(t_C+273.15);
end
% plotting
plot(tinv,log_p_mmHg,'-ro')
% labeling the axis
xlabel('1/T [1/K]')
ylabel('log10 p/mmHg')
% plot title
title('Clapeyron plot for ethanol for 0 to 100 C')
% Saving and printing to file
savefig('Exercise_4_9c.fig')
print('-depsc','Exercise_4_9c.eps')
122
Chapter 4 Logic and vectors
While in (c) we “shift up by 1”, this may fail if the temperature range were to change
and would certainly fail if we decided to increment the temperature by a value
other than 1. Next, let’s consider two alternatives. First, the use of a counter variable is a robust and general solution to overcome these limitations. Here’s what (c)
might look like using a counter variable (c):
Listing 4.19 Example_4_9c_counter.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
%
%
%
%
%
%
%
%
%
%
%
Plotting the vapor pressure of ethanol using Antione's equation
from 0 to 100 oC. In this version of the code we will use
a counter variable.
Dr. Paluch, Example 4.9c
Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
oC) stored to variables a, b, and c.
Post-condition: Plot of log10 psat vs 1/T where T is in units of K
and psat is in units of mmHg
% Start by clearing our figure, just in case
clf
% Clear/delete our vectors in case we previously ran with a different,
% larger temperature range
clear log_p_mmHg tinv t_C
% Initialize our counter variable
n = 0;
% We need to calculated log psat over the range 0 to 100 C
for t_C = 0:100
% Increment the counter variable by 1 each loop iteration
n = n+1;
% Calcutating logPsat, with Psat in mmHg
log_p_mmHg(n) = a-b/(t_C+c);
% Calculating 1/T with T in units of K
tinv(n) = 1/(t_C+273.15);
end
% plotting
plot(tinv,log_p_mmHg,'-ro')
% labeling the axis
xlabel('1/T [1/K]')
ylabel('log10 p/mmHg')
% plot title
title('Clapeyron plot for ethanol for 0 to 100 C')
% Saving and printing to file
savefig('Example_4_9c_counter.fig')
print('-depsc','Example_4_9c_counter.eps')
4.21 CPB Examples
123
By counter variable, I just mean using a variable to keep track of the loop iteration
number.
But we can do even better. Let’s start by creating a vector of our temperatures, then
we can just loop over the vector indices. This will also allow us to pre-size all of the
vectors used.
Listing 4.20 Example_4_9c_vector.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
%
%
%
%
%
%
%
%
%
%
%
Plotting the vapor pressure of ethanol using Antione's equation
from 0 to 100 oC. In this version of the code we will created a vector of
temperatures, then loop over the vector indices.
Dr. Paluch, Example 4.9c
Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
oC) stored to variables a, b, and c.
Post-condition: Plot of log10 psat vs 1/T where T is in units of K
and psat is in units of mmHg
% Start by clearing our figure, just in case
clf
% Clear/delete our vectors in case we previously ran with a different,
% larger temperature range
clear log_p_mmHg tinv t_C
% Creating our vector of temperatures in C. We can use the standard colon
% operator, or we could use linspace. Let's use the colon operator to be
% consistent with our previous solutions.
t_C = 0:100;
% Pre-size the over vectors used
log_p_mmHg = zeros(1,length(t_C));
tinv = zeros(1,length(t_C));
% Looping over our temperatures
for i=1:length(t_C)
% Calcutating logPsat, with Psat in mmHg
log_p_mmHg(i) = a-b/(t_C(i)+c);
% Calculating 1/T with T in units of K
tinv(i) = 1/(t_C(i)+273.15);
end
% plotting
plot(tinv,log_p_mmHg,'-ro')
% labeling the axis
xlabel('1/T [1/K]')
ylabel('log10 p/mmHg')
% plot title
title('Clapeyron plot for ethanol for 0 to 100 C')
124
Chapter 4 Logic and vectors
47 % Saving and printing to file
48 savefig('Example_4_9c_vector.fig')
49 print('-depsc','Example_4_9c_vector.eps')
And in the spirit of “Spoiling the fun”
Listing 4.21 Example_4_9c_vector_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
%
%
%
%
%
%
%
%
%
%
%
Plotting the vapor pressure of ethanol using Antione's equation
from 0 to 100 oC. In this version of the code we will created a vector of
temperatures, and then spoil all of the fun with vector operations.
Dr. Paluch, Example 4.9c
Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
oC) stored to variables a, b, and c.
Post-condition: Plot of log10 psat vs 1/T where T is in units of K
and psat is in units of mmHg
% Start by clearing our figure, just in case
clf
% Clear/delete our vectors in case we previously ran with a different,
% larger temperature range
clear log_p_mmHg tinv t_C
% Creating our vector of temperatures in C. We can use the standard colon
% operator, or we could use linspace. Let's use the colon operator to be
% consistent with our previous solutions. Notice in this version of the
% script I will update the variable names to be consistent with the
% convention of using capital letters to indicate vectors.
T_C = 0:100;
% Calculating logPsat, with Psat in mmHg using elementwise operations
Log_p_mmHg = a-b./(T_C+c);
% Calculating 1/T with T in units of K
Tinv = 1./(T_C+273.15);
% plotting
plot(Tinv,Log_p_mmHg,'-ro')
% labeling the axis
xlabel('1/T [1/K]')
ylabel('log10 p/mmHg')
% plot title
title('Clapeyron plot for ethanol for 0 to 100 C')
% Saving and printing to file
savefig('Example_4_9c_vector_2.fig')
print('-depsc','Example_4_9c_vector_2.eps')
4.21 CPB Examples
125
Example 4.10
We have now worked with the Maxwell-Boltzmann equation in every chapter now:
Example 1.6,2.5,and 3.10. In Example 3.10 you wrote a script to calculate the
average speed of water and ethanol over the range 0 to 100 ◦ C and plotted the
results. Here, repeat the calculations but now store the results to vectors. Then
create a plot of your results. Use a different symbol and/or line to represent water
and ethanol. Label your axis, give your plot a title, and add a legend. Note you will
be plotting two sets of data. If you issue two separate plot commands, you will
need to use hold on.
Solution:
Similar to (c) in Example 4.9, here we are updating Listing 3.13 to store the results
of each loop iteration to a vector, and then plot the vector results. Remember that
vector indices start at 1. So when my loop index starts at 0, I will need to shift up
by 1 to refer to the appropriate vector index.
Listing 4.22 Example_4_10.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
%
%
%
%
%
%
%
%
%
%
Plotting the average speed of water and ethanol molecules over the
range 0 to 100 oC
Dr. Paluch, Example 4.10
Pre-condition: None. I will hardcode everything since we
are asked for a specific temperature range and
the two species are specified.
Post-condition: Plot of average molecular speed versus temperature
% Start by clearing our figure
clf
% Need hold on because we will plot two sets of data
hold on
% gas constant in J/(mol K)
r = 8.314;
% Molecular weight of water and ethanol in kg/mol
mw_water = 18/1000;
mw_ethanol = 46/1000;
% Looping over the range 0 to 100 C and calculating the
% average molecular speed. Note our Maxwell-Boltzmann
% equation uses K for temperature, so we will perform
% the conversion each iteration.
for t_C = 0:100
% Temperature in K
t_K = t_C + 273.15;
% Speed of water in m/s
% Remember, vector indices start at 1, so we need to shift
% up by 1
speed_water(t_C+1) = sqrt( 3*r*t_K/mw_water );
speed_ethanol(t_C+1) = sqrt( 3*r*t_K/mw_ethanol );
126
Chapter 4 Logic and vectors
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
end
% Plotting
% When plotting, we will use the temperature in C, since
% that is what the problem statement asked for. We can
% easily create the temperature vector
t_C = 0:100;
% Water then ethanol
plot(t_C,speed_water,'-ro')
plot(t_C,speed_ethanol,'-bx')
% labeling the axis
xlabel('T [C]')
ylabel('speed [m/s]')
% plot title
title('Average speed from Maxwell-Boltzmann')
% legend.
% Data is plotted water then ethanol
legend('water','ethanol')
% Save and print
savefig('Example_4_10.fig')
print('-depsc','Exercise_4_10.eps')
4.21 CPB Examples
127
As mentioned in (c) in Example 4.9, there are some potential shortcomings of just
shifting up by 1. A more general solution would be to use a loop counter variable.
Here is what that might look like:
Listing 4.23 Example_4_10_counter.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
%
%
%
%
%
%
%
%
%
%
%
Plotting the average speed of water and ethanol molecules over the
range 0 to 100 oC. In this version of the code I will use a loop
counter variable.
Dr. Paluch, Example 4.10
Pre-condition: None. I will hardcode everything since we
are asked for a specific temperature range and
the two species are specified.
Post-condition: Plot of average molecular speed versus temperature
% Start by clearing our figure
clf
% Need hold on because we will plot two sets of data
hold on
% gas constant in J/(mol K)
r = 8.314;
% Molecular weight of water and ethanol in kg/mol
mw_water = 18/1000;
mw_ethanol = 46/1000;
% Initializing our loop counter variable
n = 0;
% Looping over the range 0 to 100 C and calculating the
% average molecular speed. Note our Maxwell-Boltzmann
% equation uses K for temperature, so we will perform
% the conversion each iteration.
for t_C = 0:100
% Increment the loop counter variable by 1 each loop iteration
n = n+1;
% Temperature in K
t_K = t_C + 273.15;
% Speed of water in m/s
speed_water(n) = sqrt( 3*r*t_K/mw_water );
speed_ethanol(n) = sqrt( 3*r*t_K/mw_ethanol );
end
% Plotting
% When plotting, we will use the temperature in C, since
% that is what the problem statement asked for. We can
% easily create the temperature vector
t_C = 0:100;
% Water then ethanol
plot(t_C,speed_water,'-ro')
plot(t_C,speed_ethanol,'-bx')
128
Chapter 4 Logic and vectors
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
% labeling the axis
xlabel('T [C]')
ylabel('speed [m/s]')
% plot title
title('Average speed from Maxwell-Boltzmann')
% legend.
% Data is plotted water then ethanol
legend('water','ethanol')
% Save and print
savefig('Example_4_10_counter.fig')
print('-depsc','Exercise_4_10_counter.eps')
4.21 CPB Examples
129
In the spirit of improving, let’s improve it some more! Since we create a temperature vector before we plot, we might as well move it up toward the top of the
program. Then for the for loop, we can just loop over the vector indices. This will
also allow us to pre-size all of our vectors.
Listing 4.24 Example_4_10_vector.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
%
%
%
%
%
%
%
%
%
%
%
%
Plotting the average speed of water and ethanol molecules over the
range 0 to 100 oC. In this version of the code I define the temperature
vector at the beginning, then loop over the vector indices. This will
also allow us to pre-size all of our vectors.
Dr. Paluch, Example 4.10
Pre-condition: None. I will hardcode everything since we
are asked for a specific temperature range and
the two species are specified.
Post-condition: Plot of average molecular speed versus temperature
% Start by clearing our figure
clf
% Need hold on because we will plot two sets of data
hold on
% Creating our vector of temperatures in C. Here I will use colon operator,
% but linspace would be another good option. Also, I will update the
% notation to use capital letters for vector variables.
T_C = 0:100;
% While we are at it, let's compute a vector of temperatues in K since we
% will need them in the calculations.
T_K = T_C + 273.15;
% Now we can pre-size all of our other vectors. Again, note the update to
% the variable names to coincide with the use of capital letters to
% indicate vectors.
Speed_water = zeros(1,length(T_C));
Speed_ethanol = zeros(1,length(T_C));
% gas constant in J/(mol K)
r = 8.314;
% Molecular weight of water and ethanol in kg/mol
mw_water = 18/1000;
mw_ethanol = 46/1000;
% Looping over the temperature range. We will look using vector indices. I
% will use T_K since temperature in K is needed for the calculations.
for i=1:length(T_K)
% Speed of water in m/s
Speed_water(i) = sqrt( 3*r*T_K(i)/mw_water );
Speed_ethanol(i) = sqrt( 3*r*T_K(i)/mw_ethanol );
end
130
Chapter 4 Logic and vectors
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
% Plotting
% When plotting, we will use the temperature in C, since
% that is what the problem statement asked for.
% Water then ethanol
plot(T_C,Speed_water,'-ro')
plot(T_C,Speed_ethanol,'-bx')
% labeling the axis
xlabel('T [C]')
ylabel('speed [m/s]')
% plot title
title('Average speed from Maxwell-Boltzmann')
% legend.
% Data is plotted water then ethanol
legend('water','ethanol')
% Save and print
savefig('Example_4_10_vector.fig')
print('-depsc','Exercise_4_10_vector.eps')
4.21 CPB Examples
131
Excellent! And now in the spirit of “Spoiling the fun”
Listing 4.25 Example_4_10_vector_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
%
%
%
%
%
%
%
%
%
%
%
%
Plotting the average speed of water and ethanol molecules over the
range 0 to 100 oC. In this version of the code I define the temperature
vector at the beginning, and then perform the calculations using
elementwise operations.
Dr. Paluch, Example 4.10
Pre-condition: None. I will hardcode everything since we
are asked for a specific temperature range and
the two species are specified.
Post-condition: Plot of average molecular speed versus temperature
% Start by clearing our figure
clf
% Need hold on because we will plot two sets of data
hold on
% Creating our vector of temperatures in C. Here I will use colon operator,
% but linspace would be another good option. Also, I will update the
% notation to use capital letters for vector variables.
T_C = 0:100;
% While we are at it, let's compute a vector of temperatues in K since we
% will need them in the calculations.
T_K = T_C + 273.15;
% gas constant in J/(mol K)
r = 8.314;
% Molecular weight of water and ethanol in kg/mol
mw_water = 18/1000;
mw_ethanol = 46/1000;
% Now perform the calculations using elementwise operations
% Speed of water in m/s
Speed_water = sqrt( 3*r*T_K/mw_water );
Speed_ethanol = sqrt( 3*r*T_K/mw_ethanol );
% Plotting
% When plotting, we will use the temperature in C, since
% that is what the problem statement asked for.
% Water then ethanol
plot(T_C,Speed_water,'-ro')
plot(T_C,Speed_ethanol,'-bx')
% labeling the axis
xlabel('T [C]')
ylabel('speed [m/s]')
% plot title
title('Average speed from Maxwell-Boltzmann')
132
Chapter 4 Logic and vectors
52
53
54
55
56
57
58
59
% legend.
% Data is plotted water then ethanol
legend('water','ethanol')
% Save and print
savefig('Example_4_10_vector_2.fig')
print('-depsc','Exercise_4_10_vector_2.eps')
4.22 Glossary
compound: A statement, like if and for, that contains other statements in an
indented body.
nesting: Putting one compound statement in the body of another.
relational operator: An operator that compares two values and generates a logical value as a result.
logical value: A value that represents either “true” or “false”. MATLAB uses the
values 1 and 0, respectively.
flag: A variable that contains a logical value, often used to store the status of
some condition.
scalar: A single value.
vector: A sequence of values.
matrix: A two-dimensional collection of values (also called “array” in some MATLAB documentation).
index: An integer value used to indicate one of the values in a vector or matrix
(also called subscript in some MATLAB documentation).
element: One of the values in a vector or matrix.
elementwise: An operation that acts on the individual elements of a vector or
matrix (unlike some linear algebra operations).
reduce: A way of processing the elements of a vector and generating a single
value; for example, the sum of the elements.
apply: A way of processing a vector by performing some operation on each of
the elements, producing a vector that contains the results.
search: A way of processing a vector by examining the elements in order until
one is found that has the desired property.
4.23 Exercises
4.23 Exercises
Exercise 4.1 In Exercise 2.1 and 3.1 we created scripts to compute the surface tension
of ethanol, propane, and ethanol. Let’s keep the fun going here. First, let’s update our
script from Exercise 3.1 in (a) and (b). Then in (c) and (d) we will update our script from
Exercise 2.1.
a) Update your scripts so that the results are stored to vectors. You should have a total
of six vectors: the temperatures and corresponding surface tensions for ethanol, the
temperatures and corresponding surface tensions for propane, and the temperature
and corresponding surface tensions for acetone. Plot the vectors. Does this make it
easier to add a legend?
b) Next, update your script to eliminate the use of for loops. Instead, use elementwise
operations.
c) We wish to create a script to compute the surface tension of ethanol, propane, or
acetone, as specified by the user. How do you accomplish this? I would create a “flag”
variable to which the user should assign an integer value, where each integer maps to
a species. For example, if the flag variable is set to 1, that corresponds to performing
the calculations for ethanol. The calculation will be performed at a single temperature
as specified by the user.
d) Last, update your script to check that the temperature is within the range of applicability of the equation.
Exercise 4.2 Let’s update our script from Exercise 3.2. Recall that we computed the
enthalpy of vaporization as a function of carbon number for the homologous series
of linear alkanes. In this version of the script, store the results to vectors. Use one
vector to store the carbon number, and another to store the corresponding enthalpy of
vaporization. Plot the results as before.
Once you script is working, have a look at documentation pages for diff. Use diff
to compute the changes in enthalpy of vaporization. Are they constant?
Exercise 4.3 Update your script from Exercise 3.3. First, update your code to eliminate
the use of a for loop. To accomplish this, you should create a vector of temperatures and
then use elementwise operations. To create a vector of temperatures from Tmin to Tmax ,
you might find the function linspace useful. Store results to a vector. Then create a plot
using your vectors.
Exercise 4.4 Update your script from Exercise 3.4, our friction factor problem. We will
update it step-by-step. Each step you will be able to run and check your code to make
sure nothing has changed. At the end of the problem we still will not make a nice Moody
diagram, but we will be closer to being able to accomplish this goal.
1. First, update you script so that each loop iteration, the computed friction factor is
stored to a vector.
2. Next, rather than use Re as a loop index variable, at the beginning of your script
create a vector of Re values. Then in your loop, loop over the indices (1 to n) of this
vector and use the appropriate element each iteration.
3. Next, update your code to use elementwise operations to eliminate the need for a
for loop.
133
134
Chapter 4 Logic and vectors
With the use of elementwise operations, you should find that your code is much
more efficient and hopefully more compact. Before getting to the creation of a Moody
diagram, we will discuss more the creation of our vector of Re values for a desired, larger
range, and we will also need log axes.
Chapter
5
Functions
In Chapter 5 we continue to build our foundational knowledge of MATLAB with
the introduction of functions. The use of functions will be very important as we
continue to work through the course. A function is like a script, but it has its own
workspace. Any information you want your function to have from your workspace
needs to be passed to it, and any information you want the function to share
with your workspace needs to be returned. Many of MATLAB’s built-in numerical
methods, from zero finding to solving ordinary differential equations, require us
to write functions. By the end of this chapter you will be able to:
• Explain the difference between a function and a script
• Demonstrate the ability to write a function
• Apply the use of functions to solve basic engineering problems
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
5.1 Name Collisions
Remember that all of your scripts run in the same workspace, so if one script
changes the value of a variable, all your other scripts see the change. With a small
number of simple scripts, that’s not a problem, but eventually the interactions
between scripts become unmanageable.
For example, the following (increasingly familiar) script computes the sum
of the first n terms in a geometric sequence, but it also has the side-effect of
assigning values to a1, total, i and a.
135
136
Chapter 5 Functions
a1 = 1;
total = 0;
for i=1:10
a = a1 * 0.5^(i-1);
total = total + a;
end
ans = total
If you were using any of those variable names before calling this script, you might
be surprised to find, after running the script, that their values had changed. If you
have two scripts that use the same variable names, you might find that they work
separately and then break when you try to combine them. This kind of interaction
is called a name collision.
As the number of scripts you write increases, and they get longer and more
complex, name collisions become more of a problem. Avoiding this problem is
one of the motivations for functions.
In the example above, after storing the final result to ans we could clear all
of the variables used: clear a1, total, i, a. This is one of the main effect of
using a function.
5.2 Functions
A function is like a script, except
• Each function has its own workspace, so any variables defined inside a
function only exist while the function is running, and don’t interfere with
variables in other workspaces, even if they have the same name. (See the
MATLAB documentation page “Base and Function Workspaces”).
• Function inputs and outputs are defined carefully to avoid unexpected
interactions.
t Just as with for loops and
if statements, there is no
need for a semicolon after
the signature line.
To define a new function, you create an M-file with the name you want, and put a
function definition in it. For example, to create a function named myfunc, create
an M-file named myfunc.m and put the following definition into it.
Listing 5.1 myfunc.m
1 function res = myfunc(x)
2
s = sin(x)
3
c = cos(x)
4
res = abs(s) + abs(c)
5 end
The first word of the file has to be the word function, because that’s how MATLAB tells the difference between a script and a function file. (See the MATLAB
documentation page “Create Functions in Files”.)
5.2 Functions
137
You can create a new M-file in MATLAB’s editor using the New Script button,
just as we have used previously to create a script; this is what I will use throughout
the text. Alternatively, you can use the New button, then select Function from
the dropdown menu. This will create a new file as before, but will also include a
template for creating a function.
t When you use the
New
button to create a new function, you will see it puts the
output variable in brackets.
Brackets are required if you
want more than one output variable. But more on
having multiple/optional
output variables later as
there is more than meets
eye.
Figure 5.1 Creating a new function.
A function definition is a compound statement. The first line is called the
signature of the function; it defines the inputs and outputs of the function. In this
case the input variable is named x. When this function is called, the argument
provided by the user will be assigned to x.
The output variable is named res, which is short for “result.” You can call
the output variable whatever you want, but as a convention, I like to call it res.
Usually the last thing a function does is assign a value to the output variable.
Once you have defined a new function, you call it the same way you call
built-in MATLAB functions. If you call the function as a statement, MATLAB puts
the result into ans:
>> myfunc(1)
s = 0.84147
c = 0.54030
res = 1.3818
ans = 1.3818
But it is more common (and better style) to assign the result to a variable:
>> y=myfunc(1)
s = 0.84147
c = 0.54030
res = 1.3818
y = 1.3818
t Back in Chapter 2: Scripts,
I mentioned that I always
find that students like to
use the “Run” button to
run their scripts. However,
when you have a function
with input variables, you
can not simply click the
Run button. Doing so is
equivalent to typing the
name of the M-file in the
Command Window . The function requires an input, so
this will not work. You need
to click on the drop-down
arrow and select “Run: type
code to run”. This is demonstrated in the screen cast
available here. But at this
point, why not just get in
the habit of running your
scripts and functions from
the Command Window ?
138
Chapter 5 Functions
What will happen if we attempt to suppress output by executing the function
as y=myfunc(1);? Not what you might expect. We will suppress the result of the
function call, y = 1.3818, but the other intermediate lines will still be displayed
since they are not suppressed within the function file.
>> y=myfunc(1);
s = 0.84147
c = 0.54030
res = 1.3818
t Notice that I changed both
the name of the M-file and
the name of the function.
We will discuss my reason
for this momentarily.
While you are debugging a new function, you might want to display intermediate results like this, but once it is working, you will want to add semi-colons
to make it a silent function. Most built-in functions are silent; they compute a
result, but they don’t display anything (except sometimes warning messages).
Listing 5.2 myfunc_silent.m
1 function res = myfunc_silent(x)
2
s = sin(x);
3
c = cos(x);
4
res = abs(s) + abs(c);
5 end
>> y=myfunc_silent(1)
y = 1.3818
Each function has its own workspace, which is created when the function
starts and destroyed when the function ends. If you try to access (read or write)
the variables defined inside a function, you will find that they don’t exist.
>> clear variables
>> y=myfunc(1);
s = 0.84147
c = 0.54030
res = 1.3818
>> who
Your variables are: y
>> s
Undefined function or variable 's'.
The only value from the function that you can access is the result, which in this
case is assigned to y.
If you have variables named s or c in your workspace before you call myfunc,
they will still be there when the function completes.
5.3 Documentation
139
>> s = 1;
>> c = 1;
>> y = myfunc(1);
s = 0.84147
c = 0.54030
res = 1.3818
>> s, c
s = 1
c = 1
So inside a function you can use whatever variable names you want without
worrying about collisions.
5.3 Documentation
At the beginning of every function file, you should include a comment that explains what the function does.
Listing 5.3 myfunc_comment.m
1
2
3
4
5
6
7
8
9
% res = myfunc_comment(x)
% compute the Manhattan distance from the origin to the
% point on the unit circle with angle (x) in radians.
function res = myfunc_comment(x)
s = sin(x);
c = cos(x);
res = abs(s) + abs(c);
end
When you ask for help, MATLAB prints the comment you provide.
>> help myfunc_comment
res = myfunc_comment(x)
Compute the Manhattan distance from the origin to the
point on the unit circle with angle (x) in radians.
There are lots of conventions about what should be included in these comments. Among other things, it is a good idea to include:
• The signature of the function, which includes the name of the function, the
input variable(s) and the output variable(s).
• A clear, concise, abstract description of what the function does. An abstract
description is one that leaves out the details of how the function works,
t When you use the
New
button to create a new function, you will see it puts the
documentation material
after the function signature.
Both formats are correct
and have the same effect
(give it a try). I prefer to put
the comments/documentation at the top of a file,
the same convention we
used when writing scripts.
This is also why I use the
New Script button to create
function files. Personally,
the least I need to try and
remember the better.
140
Chapter 5 Functions
and includes only information that someone using the function needs to
know. You can put additional comments inside the function that explain
the details.
• An explanation of what the input variables mean; for example, in this case
it is important to note that x is considered to be an angle in radians.
• Any preconditions and postconditions.
t This is a very common mistake made by beginners
and can make debugging
tricky. Remember the first
step of Incremental Development. Start by creating a
function that takes no input
and performs simple task,
such as res =5. Then call
the function from the command line. This way you
make sure the function you
think you are calling is what
is actually being called.
5.4 Function names
There are three “gotchas” that come up when you start naming functions. The
first is that the “real” name of your function is determined by the file name, not by
the name you put in the function signature. As a matter of style, you should make
sure that they are always the same, but if you make a mistake, or if you change
the name of a function, it is easy to get confused.
In the spirit of making errors on purpose, create a copy myfunc_silent.m and
Save As myfunc_name.m. In myfunc_name.m, change the name of the function
to something_else, and then run it again.
If this is what you put in myfunc_name.m:
Listing 5.4 myfunc_name.m
1 function res = something_else(x)
2
s = sin(x);
3
c = cos(x);
4
res = abs(s) + abs(c);
5 end
Then here’s what you’ll get:
t When you create a new
M-file in the MATLAB Editor and start by writing
the function signature,
MATLAB will suggest the
name of the function as the
name of the file. It will help
you try to avoid the “first
gotcha.”
t In MATLAB 2015a, MATLAB will display a warning
message in a new pop-up
window if you attempt the
“second gotcha,” and will
suggest a change to avoid it.
You can fix the name right
there in the pop-up window.
(To be clear, I am referring
to when you try to save the
file in the Editor, not when
you try to run it.)
>> y = myfunc_name(1)
y = 1.3818
>> y = something_else(1)
Undefined command/function 'something_else'.
The second gotcha is that the name of the file can’t have spaces. We encountered the same rule when naming variables, where we used underscores in place
of spaces when desired. More generally, valid function names follow the same
rules as variable names. For example, if you write a function and name the file my
func.m, which the MATLAB editor will happily allow you to do, and then try to
run it, you get:
>> y = my func(1)
Undefined function or variable 'my'.
5.4 Function names
141
For the first two gotcha’s, I would again point you to the MATLAB documentation
page “Create Functions in Files.”
The third gotcha is that your function names can collide with built-in MATLAB
functions. For example, if you create an M-file named sum.m and save it in your
current folder, then MATLAB will always call this new function and not the built-in
version. This is a bad idea, especially if you plan to share your code with others.
The unsuspecting user may use the command sum and expect to get MATLAB’s
built-in function, but instead get the function you created. As an example, put
the following code in a file named sum.m:
Listing 5.5 sum.m
1 function res = sum(x)
2
res = 7;
3 end
And then try this:
>> sum(1:3)
ans = 7
While this is correct using your code, it is confusing to the unknowing user. Before
you create a new function, check to see if there is already a MATLAB function with
the same name. If there is, choose another name!
MATLAB will attempt to help you if you attempt the third gotcha. When I created the function sum, the editor happily allowed me to save the file. However, the
following message was displayed in the Command Window : Warning: Function
sum has the same name as a MATLAB built-in. We suggest you rename
the function to avoid a potential name conflict.
Note that beside name collisions with other functions, name collisions with
variables exist too! So take a minute when you name your function. For more
insight on the third gotcha, have a look at the MATLAB documentation pages
“Files and Folders that MATLAB Accesses and “Function Precedence Order.”
Example 5.1
MATLAB contains a function sqrt to compute the square root of a number. Write
a function named cubert to compute the cube root of a number.
Solution: (Link to screen cast and accompanying M-file.) The cube root of x is
x 1/3 . Let’s write the function.
Listing 5.6 cubert.m
1
2
3
4
5
% res = cubert (n)
% Computing the cube root of a number n.
function res = cubert (n)
res = n^(1/3);
end
t Before we move on to Examples, recall from our
discussion in Chapter 4 that
MATLAB’s built-in functions operate element-wise
on vectors. The same is
true with functions that
you write, so long as you
use element-wise operations (i.e., .*). Remember,
a scalar is a 1 by 1 matrix,
so element-wise operators
work as normal with scalars.
So writing your functions
in this way is not a problem.
However, in this Chapter we
will only consider functions
that pass and return scalars.
Functions of vectors will be
discussed later.
142
Chapter 5 Functions
Beginning with a new MATLAB session, let’s first take a look at our workspace.
>> who
>>
Nothing. We currently do not have any defined variables in our workspace. Now
let’s use our function cubert to compute the cube root of 27, which we know
should be 3.
>> cubert(27)
ans = 3
If we again take a look at our workspace:
>> who
Your variables are:
ans
The only defined variable in our workspace is ans, the default variable that the
result of a calculation is stored to. Notice that while res and n are used in our function, neither appear in our workspace. While ans is a great variable, the problem is
it will be over-written the next time we perform a calculation. It is a better practice
to store the result of the calculation to a unique variable.
>> x = cubert(27)
x = 3
Lastly, remember that a variable is equal to the number that is assigned to it. So
you can also pass variables to functions. This is especially good if it makes your
code more readable. And the variable need not be n, what we call the passed variable within the function; MATLAB doesn’t “see” the variable name, just the number.
>> k = 27;
>> x = cubert(k)
x = 3
In addition to calling a variable from the Command Window , you can call it from
a script. (You may find this useful when submitting homework solutions.) After
all, a script is just a series of commands you could enter one-by-one from the
Command Window . Additionally, you could call a function from within a function.
And beginning with MATLAB 2016b, you can now add functions to scripts (i.e., put
a “local” function directly in a script file). We will discuss multiple functions in a
file in the next chapter.
5.5 Multiple input variables
143
Example 5.2
MATLAB contains a function sin which computes the sine of an angle x, where x
is in units of radians, and a function sind where the angle is in units of degrees.
Write a new function sin_deg which computes the sine of an angle x in degrees.
(I.e., Your own version of sind.) Your function should convert the angle from
radians to degrees, and then use sin to compute the sine of the angle.
Note: In practice I would always recommend using MATLAB’s built-in functions,
so long as you know they exist and how they work. This way you can be guaranteed they are bug free and optimized for efficiency. Here, this is an academic
exercise, and the availability of sind provides a means for you to directly check
the correctness of your code.
Solution: (Link to screen cast and accompanying M-file.) While it is unnecessary, let’s first write a function to convert an angle from degrees to radians, call
it deg_to_rad. We will then call this function from within sin_deg. A simple
example of calling a function from within a function.
Listing 5.7 deg_to_rad.m
1
2
3
4
5
% res = deg_to_rad(n)
% Converting an angle from degrees to radians
function res = deg_to_rad(n)
res = n*pi/180;
end
Listing 5.8 sin_deg.m
1
2
3
4
5
6
% res = sin_deg(n)
% Computing the sin of angle n in units of degress
function res = sin_deg(n)
x = deg_to_rad(n);
res = sin(x);
end
>> y = sin_deg(90)
y = 1
>> y = sin_deg(180)
y = 1.2246e-16
5.5 Multiple input variables
Functions can, and often do, take more than one input variable. For example, the
following function takes two input variables, a and b:
144
Chapter 5 Functions
Listing 5.9 hypotenuse.m
1
2
3
4
5
6
% res = hypotenuse(a, b)
% compute the hypotenuse of a right triangle with sides of
% length a and b.
function res = hypotenuse(a, b)
res = sqrt(a^2 + b^2);
end
This function computes the length of the hypotenuse of a right triangle if the
lengths of the adjacent sides are a and b using the Pythagorean Theorem. (Note:
there is a MATLAB function called hypot that does the same thing.)
If we call it from the Command Window with arguments 3 and 4, we can
confirm that the length of the third side is 5.
>> c = hypotenuse(3, 4)
c = 5
The arguments you provide are assigned to the input variables in order, so in this
case 3 is assigned to a and 4 is assigned to b. MATLAB checks that you provide
the right number of arguments; if you provide too few, you get
>> c = hypotenuse(3)
Error using hypotenuse (line 2)
Not enough input arguments.
This error message might seem confusing if you were just to read the first line,
because it suggests that the problem is in hypotenuse rather than in the function
call. But the second line should clear-up what the problem is. Keep that in mind
when you are debugging.
If you provide too many arguments, you get
>> c = hypotenuse(3, 4, 5)
Error using hypotenuse
Too many input arguments.
Example 5.3
Write a function rect_area to compute the area of a rectangle of length l and
height h.
Solution: (Link to screen cast and accompanying M-file.) For a rectangle, the area
A is computed as A = l h. Let’s write a function to do this for us.
5.5 Multiple input variables
145
Listing 5.10 rect_area.m
1
2
3
4
5
% res = rect_area (l,h)
% Computing the area of a rectangle of length l and heigh h.
function res = rect_area(l,h)
res = l*h;
end
>> a = rect_area(2,4)
a = 8
Note that while I sometimes deviate from my own suggestion, a good habit is to
use lowercase letter variables for scalar quantities, and capital letter variables for
vectors/matrices. Remember MATLAB is case-sensitive, so this will keep you from
mixing things up. So while A for area is attractive, I instead use a since it is a scalar
quantity.
Example 5.4
Rather than having a separate function to compute the sine of an angle in radians and in degrees, create a new function sin_angle that can compute either,
depending on the specification of the user.
Solution: (Link to screen cast and accompanying M-file.) In addition to passing
the angle to the function, we will also need to pass a flag to tell the function what
units the angle is in. For this, I tend to use integers. For instance, if the flag variable
is 1 the angle is in radians, if it is -1 the angle is in degrees, and if it is any other
value, tell the user they didn’t correctly indicate their choice of units. You might
also think of using a string. Say ‘deg’ for degrees ‘rad’ for radians. This could work
for this particular example. But in general, you need to be careful when using
strings in condition statements, as the length of the strings being compared need
to agree.
146
Chapter 5 Functions
Listing 5.11 sin_angle.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
% res = sin_angle (x,units)
% Computing the sine of angle x. The units of x are determined by
% the variable units.
%
if units == 1, we are using radians
%
if units == -1, we are using degrees
%
else you made a mistake!
function res = sin_angle (x,units)
if units == 1
res = sin(x);
elseif units == -1
res = sin_deg(x); % Could instead use built-in sind(x)
else
disp('Incorrect choice of units. Please try again.')
res = NaN;
end
end
Notice two things. First, I use the function disp to display my error message,
which is a string. Since the output is not suppressed, it will be displayed in the
Command Window if I do not provide a valid value for units. Second, if I do not
provide a valid value of units, NaN is assigned to res. Remember NaN stands for
“Not a Number”, and will be propagated though subsequent calculations. I will run
sin_angle passing a value of units = 2 to confirm this result, and will multiply
ans by 2 and then 0 to confirm that the result is still be NaN.
>> sin_angle(180,2)
Incorrect choice of units. Please try again.
ans = NaN
>> ans*2
ans = NaN
>> ans*0
ans = NaN
Nice!
5.6 Logical functions
In Section 4.4 we used logical operators to compare values. MATLAB also provides
logical functions that check for certain conditions and return logical values: 1 for
“true” and 0 for “false”.
For example, isprime checks to see whether a number is prime.
5.6 Logical functions
147
>> isprime(17)
ans = 1
>> isprime(21)
ans = 0
The functions isscalar and isvector check whether a value is a scalar or vector;
if both are false, you can assume it is a matrix (at least for now).
To check whether a value you have computed is an integer, you might be
tempted to use isinteger. But that would be wrong, so very wrong. isinteger
checks whether a value belongs to one of the integer types (a topic we have not
discussed); it doesn’t check whether a floating-point value happens to be an integer.
>> c = hypotenuse(3, 4)
c = 5
>> isinteger(c)
ans = 0
To do that, we have to write our own logical function, which we’ll call isintegral:
Listing 5.12 isintegral.m
1
2
3
4
5
6
7
8
9
10
% res = isintegral(x)
% checks whether or not x is an integer. If it is, it returns
% a value of 1 for true. If not, it returns a value of 0 for false.
function res = isintegral(x)
if round(x) == x
res = 1;
else
res = 0;
end
end
This function is good enough for most applications, but remember that floatingpoint values are only approximately right; in some cases the approximation is an
integer but the actual value is not.
Example 5.5
Write a function iseven to check if an integer is even.
Hint: An even number is a number that when divided by 2 has a remainder of 0.
This is called the modulus after division, and MATLAB has a function to compute it,
mod. Type help mod and have a look at the documentation. If we were interested
instead in odd numbers, an odd number is a number that when divided by 2 has a
remainder of 1.
148
Chapter 5 Functions
Solution: (Link to screen cast and accompanying M-file.)
Listing 5.13 iseven.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
% res = iseven(n)
% A function to determine if integer n is even. An even number is a
% number that when divided by 2 has a remainder of 0. We will compute
% the remainder of dividing n by 2 using the MATLAB function mod. If
% mod(n,2) is found to be 0, we have an even number so set
% res = 1 (true). Otherwise, set res = 0 (false).
function res = iseven(n)
r = mod(n,2);
if r == 0
res = 1;
else
res = 0;
end
end
Let’s consider a couple of cases:
>> iseven(222)
ans = 1
>> iseven(211)
ans = 0
What if you had never heard of the term “modulus” before? Well, using the hint
that an even number is a number that when divided by 2 has a remainder of 0, we
can cleverly use the round command.
Listing 5.14 iseven_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
% res = iseven_2(n)
% A function to determine if integer n is even. An even number is a
% number that when divided by 2 has a remainder of 0. We will take the
% integer, divide by two, and then round the answer to the nearest integer.
% We will then if the rounded and original answer are equivalent. If they
% are, the we have a remainder of zero and we have an evn number, so set
% res = 1 (true). Otherwise, set res = 0 (false).
function res = iseven_2(n)
r1 = n/2;
r2 = round(r1);
if r1==r2
res = 1;
else
res = 0;
end
end
Additionally note that beside mod, MATLAB has a built-in function rem to compute
the remainder after division. Using either in this example would yield the same
5.7 An incremental development example
result. The difference between mod and rem is provided in the documentation for
rem if you are interested, under the header “Difference between rem and mod.”
5.7 An incremental development example
Let’s say that we want to write a program to search for “Pythagorean triples:”
sets of integer values, like 3, 4 and 5, that are the lengths of the sides of a right
triangle. In other words, we would like to find integer values a, b and c such that
a2 + b2 = c 2.
Here are the steps we will follow to develop the program incrementally.
• Write a script named find_triples and start with a simple statement like
x=5.
• Write a loop that enumerates values of a from 1 to 3, and displays them.
• Write a nested loop that enumerates values of b from 1 to 4, and displays
them.
• Inside the loop, call hypotenuse to compute c and display it.
• Use isintegral to check whether c is an integer value.
• Use an if statement to print only the triples a, b and c that pass the test.
• Transform the script into a function.
• Generalize the function to take input variables that specify the range to
search.
So the first draft of this program is x=5, which might seem silly, but if you start
simple and add a little bit at a time, you will avoid a lot of debugging.
Here’s the second draft:
for a=1:3
a
end
At each step, the program is testable: it produces output (or another visible effect)
that you can check.
5.8 Nested loops
The third draft contains a nested loop:
149
150
Chapter 5 Functions
for a=1:3
a
for b=1:4
b
end
end
The inner loop gets executed 3 times, once for each value of a, so here’s what the
output loops like (I adjusted the spacing to make the structure clear):
>> find_triples
a = 1
b
b
b
b
=
=
=
=
1
2
3
4
a = 2
b
b
b
b
=
=
=
=
1
2
3
4
a = 3
b
b
b
b
=
=
=
=
1
2
3
4
The next step is to compute c for each pair of values a and b.
for a=1:3
for b=1:4
c = hypotenuse(a, b);
[a, b, c]
end
end
To display the values of a, b and c, I am using a feature we haven’t seen before.
The bracket operator creates a new vector which, when it is displayed, shows the
three values on one line:
>> find_triples
ans = 1.0000
ans = 1.0000
1.0000
2.0000
1.4142
2.2361
5.9 Conditions and flags
ans
ans
ans
ans
ans
ans
ans
ans
ans
ans
=
=
=
=
=
=
=
=
=
=
1.0000
1.0000
2.0000
2.0000
2.0000
2.0000
3.0000
3.0000
3.0000
3
3.0000
4.0000
1.0000
2.0000
3.0000
4.0000
1.0000
2.0000
3.0000
4
151
3.1623
4.1231
2.2361
2.8284
3.6056
4.4721
3.1623
3.6056
4.2426
5
Sharp-eyed readers will notice that we are wasting some effort here. After
checking a = 1 and b = 2, there is no point in checking a = 2 and b = 1. We can
eliminate the extra work by adjusting the range of the second loop:
for a=1:3
for b=a:4
c = hypotenuse(a, b);
[a, b, c]
end
end
If you are following along, run this version to make sure it has the expected effect.
>> find_triples
ans = 1.0000
ans = 1.0000
ans = 1.0000
ans = 1.0000
ans = 2.0000
ans = 2.0000
ans = 2.0000
ans = 3.0000
ans = 3
4
1.0000
2.0000
3.0000
4.0000
2.0000
3.0000
4.0000
3.0000
5
1.4142
2.2361
3.1623
4.1231
2.8284
3.6056
4.4721
4.2426
5.9 Conditions and flags
The next step is to check for integer values of c. This loop calls isintegral and
prints the resulting logical value.
152
Chapter 5 Functions
for a=1:3
for b=a:4
c = hypotenuse(a, b);
flag = isintegral(c);
[c, flag]
end
end
By not displaying a and b I made it easy to scan the output to make sure that
the values of c and flag look right.
>> find_triples
ans = 1.4142
ans = 2.2361
ans = 3.1623
ans = 4.1231
ans = 2.8284
ans = 3.6056
ans = 4.4721
ans = 4.2426
ans = 5
0
0
0
0
0
0
0
0
1
I chose the ranges for a and b to be small (so the amount of output is manageable), but to contain at least one Pythagorean triple. A constant challenge of
debugging is to generate enough output to demonstrate that the code is working
(or not) without being overwhelmed.
The next step is to use flag to display only the successful triples:
for a=1:3
for b=a:4
c = hypotenuse(a, b);
flag = isintegral(c);
if flag
[a, b, c]
end
end
end
Now the output is elegant and simple:
>> find_triples
ans = 3
4
5
5.10 Encapsulation and generalization
5.10 Encapsulation and generalization
As a script, this program has the side-effect of assigning values to a, b, c and
flag, which would make it hard to use if any of those names were in use. By
wrapping the code in a function, we can avoid name collisions; this process is
called encapsulation because it isolates this program from the workspace.
In order to put the code we have written inside a function, we have to indent
the whole thing. The MATLAB editor provides a shortcut for doing that, the
Increase indent button on the Editor tab; highlight the text to indent and then
click Increase indent , our you could type Tab instead. Just don’t forget to unselect
the text before you start typing!
The first draft of the function takes no input variables:
1
2
3
4
5
6
7
8
9
10
11
function res = find_triples()
for a=1:3
for b=a:4
c = hypotenuse(a, b);
flag = isintegral(c);
if flag
[a, b, c]
end
end
end
end
The empty parentheses in the signature are not strictly necessary, but they make it
apparent that there are no input variables. Similarly, when I call the new function,
I like to use parentheses to remind me that it is a function, not a script; but again,
here too the use of the parentheses is optional since there are no input variables.
>> find_triples()
ans = 3
4
5
The output variable (res) isn’t strictly necessary, either; it never gets assigned a
value. But I put it there as a matter of habit, and also so my function signatures all
have the same structure. For now, we are just displaying the triples. Later in the
text, you will learn how you can pack all of the triples up into a matrix and return
it. But we will save that for later.
The next step is to generalize this function by adding input variables. The
natural generalization is to replace the constant values 3 and 4 with a variable so
we can search an arbitrarily large range of values.
153
154
Chapter 5 Functions
1
2
3
4
5
6
7
8
9
10
11
function res = find_triples(n)
for a=1:n
for b=a:n
c = hypotenuse(a, b);
flag = isintegral(c);
if flag
[a, b, c]
end
end
end
end
Here are the results for the range from 1 to 15:
>> find_triples(15)
ans = 3
4
5
ans = 5
12
13
ans = 6
8
10
ans = 8
15
17
ans = 9
12
15
Some of these are more interesting than others. The triples 5, 12, 13 and 8, 15, 17
are “new,” but the others are just multiples of the 3, 4, 5 triangle we already knew.
5.11 A misstep
When you change the signature of a function, you have to change all the places
that call the function, too. For example, suppose I decided to add a third input
variable to hypotenuse:
1
2
3
function res = hypotenuse(a, b, d)
res = (a.^d + b.^d) ^ (1/d);
end
When d is 2, this does the same thing it did before. There is no practical reason
to generalize the function in this way; it’s just an example. Now when you run
find_triples, you get:
5.12 continue
>> find_triples(20)
Error using hypotenuse (line 2)
Not enough input arguments.
Error in find_triples (line 7)
c = hypotenuse(a, b);
So that makes it pretty easy to find the error. This is an example of a development
technique that is sometimes useful: rather than search the program for all the
places that use hypotenuse, you can run the program and use the error messages
to guide you.
But this technique is risky, especially if the error messages make suggestions
about what to change. If you do what you’re told, you might make the error
message go away, but that doesn’t mean the program will do the right thing.
MATLAB doesn’t know what the program is supposed to do, but you should.
And that brings us to the Eighth Theorem of debugging:
Error messages sometimes tell you what’s wrong, but they seldom
tell you what to do (and when they try, they’re usually wrong).
5.12 continue
As one final improvement, let’s modify the function so that it only displays the
“lowest” of each Pythagorean triple, and not the multiples. The simplest way to
eliminate the multiples is to check whether a and b share a common factor. If
they do, then dividing both by the common factor yields a smaller, similar triangle
that has already been checked.
MATLAB provides a gcd function that computes the greatest common divisor
of two numbers. If the result is greater than 1, then a and b share a common
factor and we can use the continue statement to skip to the next pair. This brings
us to the final version of our function:
155
156
Chapter 5 Functions
Listing 5.15 find_triples.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% res = find_triples (n)
% finding Pythagorean triples over the range 1 to n.
function res = find_triples (n)
for a=1:n
for b=a:n
if gcd(a,b) > 1
continue
end
c = hypotenuse(a, b);
if isintegral(c)
[a, b, c]
end
end
end
end
continue causes the program to end the current iteration immediately (without
executing the rest of the body), jump to the top of the loop, and “continue” with
the next iteration.
In this case, since there are two loops, it might not be obvious which loop to
jump to, but the rule is to jump to the inner-most loop (which is what we wanted).
I also simplified the program slightly by eliminating flag and using isintegral
as the condition of the if statement.
Here are the results with n=40:
>> find_triples(40)
ans = 3
4
5
ans = 5
12
13
ans = 7
24
25
ans = 8
15
17
ans = 9
40
41
ans = 12
35
37
ans = 20
21
29
There is an interesting connection between Fibonacci numbers and Pythagorean
triples. If F is a Fibonacci sequence, then
2
2
(F n F n+3 , 2F n+1 F n+2 , F n+1
+ F n+2
)
is a Pythagorean triple for all n ≥ 1.
5.12 continue
157
Example 5.6
Write a function named fib_triple that takes an input variable n, uses new
function fibonacci_seq to compute the first n Fibonacci numbers, and then
checks whether this formula produces a Pythagorean triple for each number in
the sequence.
Note that fibonacci_seq will just be Listing 4.3 on page 93 converted from a
script to a function.
Solution:
Listing 5.16 fib_triple.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% res = fib_triple(n)
% checks whether the provided formula using 3 consecutive Fibonacci
% numbers produces a Pythagorean triple over the range 1 to n.
% The result will be a vector containing 1 (for true) and 0 (for false)
% for an n corresponding to the index.
function res = fib_triple(n)
F = fibonacci_seq(n+3);
for i = 1:n
a = F(i) * F(i+3);
b = 2 * F(i+1) * F(i+2);
c = F(i+1)^2 + F(i+2)^2;
% Will be 1 if true (it is a triple), 0 if not
res(i) = istriple(a, b, c);
end
end
Listing 5.17 fibonacci_seq.m
1
2
3
4
5
6
7
8
9
10
% res = fibonacci_seq(n)
% computing the fibonacci series out to n
function res = fibonacci_seq(n)
F(1) = 1;
F(2) = 1;
for i=3:n
F(i) = F(i-1) + F(i-2);
end
res = F;
end
158
Chapter 5 Functions
Listing 5.18 istriple.m
1
2
3
4
5
6
7
8
9
10
11
12
% res = istriple a, b, c)
% Checking if a, b, and c form a Pythagorean triple.
% If yes, then return 1 for true. If fale, return 0.
function res = istriple(a, b, c)
% We need to be careful using ==. If we find that
% it is not working as expected here, we can modify.
if hypotenuse(a, b) == c
res = 1;
else
res = 0;
end
end
Note that while I could combine my code into a single function, I have chosen
to modulate my code and create three separate function files. This helps make
my code more readable, and additionally helps reduce the occurrence of bugs.
Independent of fib_triple, I can write istriple and fibonacci_seq, and
test/debug them independently. Not to mention, this would facilitate the use of
istriple and fibonacci_seq by other functions too. Also note here that I have
functions with vector inputs and outputs. We will discuss this further later.
5.13 Mechanism and leap of faith
Let’s review the sequence of steps that occur when you call a function:
1. Before the function starts running, MATLAB creates a new workspace for it.
2. MATLAB evaluates each of the arguments and assigns the resulting values,
in order, to the input variables (which live in the new workspace).
3. The body of the code executes. Somewhere in the body (often the last line)
a value gets assigned to the output variable.
4. The function’s workspace is destroyed; the only thing that remains is the
value of the output variable and any side effects the function had (like
displaying values or creating a figure).
5. The program resumes from where it left off. The value of the function call
is the value of the output variable.
When you are reading a program and you come to a function call, there are
two ways to interpret it:
• You can think about the mechanism I just described, and follow the execution of the program into the function and back, or
5.14 Examples
159
• You can take the “leap of faith”: assume that the function works correctly,
and go on to the next statement after the function call.
When you use built-in functions, it is natural to take the leap of faith, in part
because you expect that the built-in MATLAB functions work, and in part because
you don’t generally have access to the code in the body of the function.
But when you start writing your own functions, you will probably find yourself
following the “flow of execution.” This can be useful while you are learning, but
as you gain experience, you should get more comfortable with the idea of writing
a function, testing it to make sure it works, and then forgetting about the details
of how it works.
Forgetting about details is called abstraction; in the context of functions,
abstraction means forgetting about how a function works, and just assuming
(after appropriate testing) that it works.
5.14 Examples
Example 5.7
Write a function isodd to check if an integer is odd. Once you are certain isodd
is working correctly, write a function that multiplies two numbers (say m and n) if
and only if only one of the numbers is odd. Otherwise, add them together.
Note, this may seem like a silly thing to do. But what I want is for you to try and use
a logical function as the condition in an if statement. Also, before writing isodd,
have a look at iseven in Example 5.5.
Solution:
Listing 5.19 isodd.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% res = isodd(n)
% A function to determine if integer n is odd. An odd number is a
% number that when divided by 2 the remainder is 1. We will compute the
% remainder of dividing n by 2 using the MATLAB function mod. If
% mod(n,2) is found to be 1, we have an odd number so set res = 1 (true).
% Otherwise, set res = 0 (false).
%
function res = isodd(n)
r = mod(n,2);
if r == 1
res = 1;
else
res = 0;
end
end
160
Chapter 5 Functions
Listing 5.20 one_odd.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
% res = one_odd(n,m)
% The function multiplies n*m if and only if only only one number is
% odd. Otherwise, it adds them together. This function uses the
% function isodd, so it must be in the same folder or in your path.
%
function res = one_odd(n,m)
% Rather than call isodd a bunch of times, call it once
% and store the result to a variable
flag_n = isodd(n);
flag_m = isodd(m);
%
% If both n and m are odd, add the numbers together
% Note that since true is equal ts 1, we can leave out the
% == 1
if flag_n && flag_m
res = n+m;
% If both n and m are not odd, they are both even,
% add the numbers together
elseif flag_n == 0 && flag_m==0
res = n+m;
% If both numbers are not odd and both numbers are not even, then
% it must be that one number is even and one number is odd. For
% this case multiply the numbers together
else
res = n*m;
end % end if
end % end function
5.15 CPB Examples
Example 5.8
Let’s keep working with Antoine’s equation (the subject of Examples 1.5, 2.4, 3.9,
and 4.9.
a) Write a function to compute p sat using the Antoine equation. The user should
provide as input variables the parameters A, B , and C , along with the desired
temperature. In your documentation, please be sure to inform the user of the
units.
b) Building on (a), could you allow the user to choose what units they would like
the pressure returned in?
c) Write a function to generate a Clapeyron plot using Antoine’s equation (i.e.,
plot log10 p sat versus 1/T ). The user should provide as input variables the
parameters A, B , and C , along with the desired temperature range (Tmin and
Tmax ). In your documentation, please be sure to inform the user of the units.
Note than when you generate a plot from within a function, it will be displayed
in a new Figure Window .
5.15 CPB Examples
161
Solution:
a) The first part to this problem is straightforward. We will create a function that
reads in the Antoine parameters A, B, and C (which assume the temperature
is in ◦ C and the pressure is in mmHg), and the temperature of interest in ◦ C,
and returns the vapor pressure in mmHg. Notice that I do use element-wise
operations, so our function is vectorized and you could pass a vector of temperatures for which to perform calculations.
Listing 5.21 antoine_p.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% res = antoine_p( a,b,c,t_C )
%
% Computing the vapor prssure of a fluid using Antoine's equation. The user
% must provide Antoine parameters A, B, and C. I will assume they are from
% the same source as the ethanol parameters we have used previously, so the
% default units will be t in degress C and p in mmHg. Please be sure to
% check this for future cases. The user must also provide the temperature
% in degrees C (t_C). The result will be pressure in mmHg.
%
% a,b,c = Antoine parameters with t in C and p in mmHg
% t_C = temperature in Celsius
% res = vapor pressure in mmHg
%
function res = antoine_p( a,b,c,t_C )
log_p_mmHg = a-b./(t_C+c);
res = 10.^(log_p_mmHg);
end
b) Next, we update our function to take on the an additional flag variable that
indicates which units to use. In my case, units will be an integer between 1
and 4. I like to use integer values for flag variables as they facilitate logical
comparisons. Rather than re-type my Antoine code, I call the function we just
wrote. While this may seem silly, it is a case of incremental development. Since
our previous code was bug free, any errors introduced must solely be do to our
logical comparisons.
162
Chapter 5 Functions
Listing 5.22 antoine_p_units.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
% res = antoine_p_units( a,b,c,t_C,units )
%
% Computing the vapor prssure of a fluid using Antoine's equation. The user
% must provide Antoine parameters A, B, and C. I will assume they are from
% the same source as the ethanol parameters used previously, so the
% default units will be t in degress C and p in mmHg. Please be sure to
% check this for future cases. The user must also provide the temperature
% in degrees C (t_C). This will result in a pressure in mmHg.
% In this version of the code, the user must also provide a value for
% units, which will indicate the desired units for the final reported
% pressure. The value of units is as follows:
%
% a,b,c = Antoine parameters with t in C and p in mmHg
% t_C = temperature in Celsius
% units = 1 for mmHg
%
= 2 for atm
%
= 3 for kPa
%
= 4 for bar
%
function res = antoine_p_units( a,b,c,t_C,units )
% First, call antoine_p to compute p in mmHg
p_mmHg = antoine_p( a,b,c,t_C );
% Checking for desired units
if units == 1 % mmHg
res = p_mmHg;
elseif units == 2 % atm
res = p_mmHg/760;
elseif units == 3 % kPa
res = p_mmHg*101.325/760;
elseif units == 4 % bar
res = p_mmHg*101.325/760/100;
else
disp('Invalid choice of units. p is returned in mmHg.')
res = p_mmHg;
end % if statement
end % function
You may wish instead to perform logical comparisons with strings. This is
possible, but you need to be careful. As we discussed previously in the text,
when you create a string, MATLAB saves the string as a vector, with the index
going 1 to the number of letters, from left to right. When comparing strings: 1)
MATLAB will compare each element for equivalence and return a logical vector
of 1’s and 0’s, 2) MATLAB is case sensitive.
c) Last but not least, we will create a function to create a Clapeyron plot. I will not
use a for loop but instead will use vector operations. In this case I directly perform the Antoine calculation, although we could just as well used our function
from the first part of this problem.
5.15 CPB Examples
163
Listing 5.23 antoine_clap_plot.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
% res = antoine_clap_plot( a,b,c,t_C_min,t_C_max )
%
% Generating a Clapeyron plot of a fluid using Antoine's equation. The user
% must provide Antoine parameters A, B, and C. I will assume they are from
% the same source as the ethanol parameters used previously, so the
% default units will be t in degress C and p in mmHg. Pleasue be sure to
% check this for future cases. The user must also provide the
% miniumum and maximum temperature in degrees C (t_C_min and t_C_max).
%
% I will then generate a Clapeyron plot over the range, or a plot of
% log10 psat vs 1/T.
% Note that here I will convert t to K.
%
% Also, note that I will not assign anything to res, so nothing will
% be returned from the function to my Workspace. Only the plot will
% be generated.
%
% a,b,c = Antoine parameters with t in C and p in mmHg
% t_C_min and t_C_max = minimum and maximum temperature in Celsius
% res = Nothing is returned
%
function res = antoine_clap_plot( a,b,c,t_C_min,t_C_max )
% Creating a vector of temperatures in C from tmin to tmax in
% increments of 1.
t_C = t_C_min:1:t_C_max;
% Computing log10 psat with psat in mmHg for each temperature.
% Here I will use elementwise operations.
log_p_mmHg =a-b./(t_C+c);
% Computing 1/t with t in K
tinv = 1./(t_C+273.15);
% Plotting
plot(log_p_mmHg,tinv)
% labeling the axis
xlabel('1/T [1/K]')
ylabel('log10 p/mmHg')
% plot title
title('Clapeyron plot')
end
Example 5.9
We wrote a function to perform Antoine equation calculations, how about the
Maxwell-Boltzmann equation? (Examples 1.6, 2.5, 3.10, 4.10). Write a function
mb_speed that takes as input molecular weight and temperature, and returns the
average molecular speed. Please be sure to communicate to the user the units of
molecular weight, temperature, and the speed.
164
Chapter 5 Functions
Solution: As was the case for our Antoine function, this problem is straightforward.
We will encapsulate and convert our Maxwell-Boltzmann script to a function.
Listing 5.24 mb_speed.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
%
%
%
%
%
%
%
%
%
%
%
%
%
function res = mb_speed( mw, t_C )
Function to compute the average molecular speed of a
species using the Maxwell-Boltzmann equation. The
user must provide the molecular weight of the species
in g/mol (or equivalently Da or amu), and the desired
temperature in oC. The result will be the speed in
m/s.
mw = molecular weight in g/mol
t_C = temperature in Celsius
res = speed in m/s
function res = mb_speed( mw, t_C )
% Convert the temperature from oC to K
t_K = t_C+273.15;
% Avogadro's number
navo = 6.022e23;
% Gas constant in J/mol K
r = 8.314;
% Boltzmann's constant, kB, in J/K
kB = r/navo;
% Calculating the mass of a molecule in kg
mass_kg = mw/navo/1000;
% Calculating the speed in m/s
res = sqrt(3*kB*t_K/mass_kg)
end
5.15 CPB Examples
165
Example 5.10
A central goal of phase-equilibrium thermodynamics is understanding, modeling,
and predicting the pvT behavior of a fluid. By pvT , we mean the relationship
between pressure (p), molar volume (v), and the temperature (T ). For a single
component, single phase system, two intensive thermodynamic properties are
needed to fix the state of the system. So if we specify T and p, we can compute
v. Put differently, v = f (T, p). Note that p, v, and T , are all intensive, so any two
could be used to fix the state of the system (and to compute the third property).
For this exercise we will write two functions to compute v at a specified T and p
using two different methods. For all cases, we will apply the methods to estimate
the molar volume of n-butane at 350 K and 2 bar for which n-butane is a superheated vapor, and at 440 K and 60 bar for which n-butane is a superheated vapor.
(Remember 1 bar = 1 × 105 Pa.) For all cases, please report v in units of cm3 /mol.
At these conditions NIST WebBook reports v = 14, 043 cm3 /mol at 350 K and 2 bar,
and v = 172.98 cm3 /mol at 440 K and 60 bar. 1 . How do your predictions compare?
Note that you are making predictions, which will disagree with the reference data.
a) Write a function to calculate v at a specified T and p using the ideal gas equation of state. Remember pV ig = nRT so that
v ig =
V ig RT
=
n
p
(5.1)
where the superscript “ig” is used to designate the property of an ideal gas.
b) The equation of state for a “real” fluid is pv = Z RT , where Z is the compressibility factor. For a real fluid then we have
v=Z
RT
= Z v ig
p
(5.2)
So we see that Z can be thought of as a correction factor that accounts for
deviations from ideal gas behavior. A first order correction that may be used to
estimate Z is the virial equation truncated after two terms
Z ≈ 1+
Bp
RT
(5.3)
where B is the second virial coefficient. Okay, so how does one compute the
second virial coefficient? One simple method is to use a correlation based on
Pitzer’s corresponding states theory
B pc
= B (0) + ωB (1)
RTc
Therefore
B=
¤
RTc £ (0)
B + ωB (1)
pc
where
B (0) = 0.083 −
1 http://webbook.nist.gov/chemistry/fluid/
0.422
Tr1.6
(5.4)
(5.5)
(5.6)
166
Chapter 5 Functions
B (1) = 0.139 −
0.172
Tr4.2
(5.7)
where Tc and p c are the critical temperature and pressure, Tr is the reduced
temperature defined as Tr = T /Tc , and ω is the accentric factor. From NIST
WebBook for n-butane we have: Tc = 425.125 K, p c = 37.960 bar, and ω = 0.201.
Write a function to calculate v at a specified T and p using the truncated virial
equation. The user should also provide as input: Tc , p c , and ω. Your function
should use the function written for (a) to compute v ig .
Solution: Let’s start with the ideal gas equation of state. Once we have this code
working, we will be able to re-use it in our virial calculation code. The function will
have input variables of the temperature and pressure. Here I will use units of bar
for pressure and K for temperature. Within the code I will convert the pressure to
SI units, perform the calculation, and then convert the volume to the desired units
of cm3 /mol.
Listing 5.25 v_ideal_gas.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
% res = v_ideal_gas(t,p_bar)
%
% Calculating the molar volume of a fluid in units of cm^3/mol using
% the ideal gas equation of state. The user must specify the
% temperature (t) in units of K, and the pressure (p) in units of bar.
% The function will then calculate the molar volume in units of cm^3/mol.
%
% t = temperature in K
% p_bar = pressure in bar
% res = molar volume in cc/mol
%
function res = v_ideal_gas( t,p_bar )
% Converting the pressure from bar to Pa so that everything
% is in SI units
p = p_bar*1e5;
% The molar gas constant in units of J/(mol K), SI units
r = 8.314;
% With all SI units, computing the molar volume in m^3/mol
v = r*t/p;
% Converting the molar volume to cm^3/mol
res = v*(100^(3));
end
Let’s use the function to compute the ideal gas molar volume at T = 350 K and
p = 2 bar.
>> t_K = 350;
>> p_bar = 2;
>> vig_cc_mol = v_ideal_gas(t_K,p_bar)
vig_cc_mol = 1.4550e+04
5.15 CPB Examples
167
This is only slightly larger than the reference value of 14,043 cm3 /mol. Next, we
will try to account for deviations from the ideal gas limit using the truncated virial
equation. Using just the second virial coefficient, this can be thought of as a
Maclaurin series expansion truncated after the second term. We will use the “real”
gas equation of state, v = Z RT /p = Z v ig . So we need just calculate Z , which is
dimensionless, then multiply by the molar volume of an ideal gas in our preferred
units. Let’s do it!
Listing 5.26 v_virial.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
% res = v_virial(t,p_bar,tc,pc_bar,omega)
%
% Calculating the molar volume of a fluid in units of cm^3/mol using the
% truncated virial equation with correspondings states to estimate
% the second virial coefficient, B.
% The user must specify the temperature (t) and critical temperature(tc)
% in units of K, the pressure (p) and critical pressure (pc) in units
% of bar, and the accentric factor (which is dimensionless).
% The function will then calculate the molar volume in units
% of cm^3/mol.
%
% t,tc = temperature and critical temperature in K
% p_bar,pc_bar = pressure and critical pressure in bar
% omega = accentric factor (dimensionless)
% res = molar volume in cc/mol
%
% Note that we will use the function v_ideal_gas to compute the
% molar volume of an ideal gas, which must be in the current path.
%
function res = v_virial( t,p_bar,tc,pc_bar,omega )
% First, we will want to work in SI units, so let's convert p_bar and
% pc_bar to Pa before we forget
p = p_bar*1e5;
pc = pc_bar*1e5;
% Molar gas constant in J/(mol K)
r = 8.314;
% Calculating the relative temperature, tr. It is important here that
% we are using absolute temperature units.
tr = t/tc;
%
% Now let's calculate B0 and B1 using the provided equations
b0 = 0.083-0.422/(tr^(1.6));
b1 = 0.139-0.172/(tr^(4.2));
% Now calculating B
b = r*tc/pc*(b0+omega*b1);
%
% Calculating the compressibility factor Z
z = 1 + b*p/(r*t);
%
% Calculating the ideal gas molar volume using our function
% v_ideal_gas which takes as input T in K and p in bar.
% It returns the molar volume in units of cm^3/mol.
vig = v_ideal_gas(t,p_bar);
%
168
Chapter 5 Functions
45
46
47
48
49 end
% Finally, calculating the molar volume of our fluid in cm^3/mol using
% the truncated virial. Note that since Z is dimensionless, multiplying
% by the ideal gas volume in cm^3/mol gives the desired result.
res = z*vig;
Now let’s test it for our system. For n-butane we are given Tc = 425.125 K, p c =
37.960 bar and ω = 0.201.
>> t_K = 350;
>> p_bar = 2;
>> tc_K = 425.125;
>> pc_bar = 37.96;
>> omega = 0.201;
>> v_cc_mol = v_virial(t_K,p_bar,tc_K,pc_bar,omega)
v_cc_mol = 1.4044e+04
Nice! The truncated virial equation predicts a molar volume of 14,044 cm3 /mol, in
excellent agreement with the reference value of 14,043 cm3 /mol.
Next, let’s look at the more challenging problem of T = 440 K and p = 60 bar where
n-butane is a supercritical fluid, and v = 172.98 cm3 /mol. Let’s look at predictions
made using the ideal gas equation of state and the truncated virial equation at
these conditions. First, the case of an ideal gas:
>> t_K = 440;
>> p_bar = 60;
>> vig_cc_mol = v_ideal_gas(t_K,p_bar)
vig_cc_mol = 609.6933
This is much larger than the reference value. Next, we will try to account for deviations from the ideal gas limit using the truncated virial coefficient.
>> t_K = 440;
>> p_bar = 60;
>> tc_K = 425.125;
>> pc_bar = 37.96;
>> omega = 0.201;
>> v_cc_mol = v_virial(t_K,p_bar,tc_K,pc_bar,omega)
v_cc_mol = 313.2382
Using the truncated virial equation, our prediction is still off from the reference
value of v = 172.98 cm3 /mol. However, the prediction is greatly improved relative
to the ideal gas equation of state.
5.16 Glossary
5.16 Glossary
side-effect: An effect, like modifying the workspace, that is not the primary purpose of a script.
name collision: The scenario where two scripts that use the same variable name
interfere with each other.
input variable: A variable in a function that gets its value, when the function is
called, from one of the arguments.
output variable: A variable in a function that is used to return a value from the
function to the caller.
signature: The first line of a function definition, which specifies the names of
the function, the input variables and the output variables.
silent function: A function that doesn’t display anything or generate a figure, or
have any other side-effects.
logical function: A function that returns a logical value (1 for “true” or 0 for
“false”).
encapsulation: The process of wrapping part of a program in a function in order
to limit interactions (including name collisions) between the function and
the rest of the program.
generalization: Making a function more versatile by replacing specific values
with input variables.
abstraction: The process of ignoring the details of how a function works in order
to focus on a simpler model of what the function does.
169
170
Chapter 5 Functions
5.17 Exercises
Exercise 5.1 “Yaws’ Critical Property Data for Chemical Engineers and Chemists”2 recommends use of a modified Watson equation to calculate the enthalpy of vaporization as
a function of temperature:
∆H
vap
¶
µ
T n
= A 1−
B
(5.8)
where A, B and n are regressed coefficients, T is the temperature in K, and ∆H vap is the
enthalpy of vaporization in units of kJ/mol.
compound
ethanol
ethane
propane
n-butane
n-pentane
n-hexane
n-heptane
A
60.8036
21.3420
26.8896
33.0198
39.8543
45.61
49.73
B
516.25
305.42
369.82
425.18
469.65
507.43
540.26
n
0.380
0.403
0.365
0.377
0.398
0.401
0.386
Tmin [K ]
300.00
90.35
85.44
134.86
143.42
177.84
182.56
Tmax [K ]
516.25
305.42
369.82
425.18
469.65
507.43
540.26
Write a function to compute ∆H vap and compare against the values computed with
your script from Exercise 2.2. Your function should take as input A, B , n and T , and
return ∆H vap .
Exercise 5.2 Let’s keep building upon your friction factor code!
In your transport phenomenon course (fluid mechanics), you likely solved many
problems that required you to read values of friction constants from a Moody chart, or to
use analytic expressions for the friction factor of limited range. Recently, Díaz-Damacillo
and Plascencia published an article in AIChE Journal titled: “A New Six Parameter Model
to Estimate the Friction Factor.”3 In that work, the authors propose a new analytic
expression containing six parameters that is capable of estimating the friction factor
for flow in pipes at all conditions (i.e., Reynold’s numbers and relative roughness). The
proposed expression takes the form:
f =
64
λ1
λ2
³
´+
³
+
τ
−Re
Re 1 + exp 1
1 + exp τ2 −Re ·
100
600
²
D
´
(5.9)
where f is the friction factor, Re is the Reynold’s number, defined as:
Re =
ρV D
µ
where ρ is the density of the fluid, V is the fluid flow velocity, and D is the diameter of the
pipe. The term ² is the pipe roughness, and the term ²/D is dimensionless and commonly
referred to as the relative roughness. In addition to Re and ²/D (two parameters), the other
six parameters are λ1 , λ2 , τ1 , and τ2 . The parameter λ1 is the residual stress contribution
2 Yaws, Carl L. (2012; 2013; 2014). Yaws’ Critical Property Data for Chemical Engineers
and Chemists. Knovel. Retrieved from https://app.knovel.com/hotlink/toc/id:kpYCPDCECD/
yaws-critical-property/yaws-critical-property
3 L. Díaz-Damacillo and G. Plascencia, AIChE J. 2019, 65, 1144–1148. DOI: 10.1002/aic.16535
5.17 Exercises
171
from the laminar to turbulant transition to the friction factor, λ2 is the residual stress
contribution from the pipe roughness to the friction factor, τ1 is the value of Re at which
occurs the first transition in the friction factor, and τ2 is the value of Re at which the
second transition occurs. The values of λ1 and τ1 are constant and equal to:
λ1 = 0.02
τ1 = 3000
and λ2 and τ2 are given by the following expressions:
!2 ¯
¯
¯
¢
¯
1
²
¯
3.7065 · D
¯
Ã
¯
1
¯
λ2 = ¯λ1 −
¡
¯
−2 log
10
0.77505 10.984
τ2 = ¡ ¢2 − ¡ ² ¢ + 7953.8
²
D
D
As you try to keep track of units, remember that Re and ²/D are dimensionless.
a) Write a function that takes as input Re and ²/D, and returns f . As a reference to
check your code, solving I find that for Re = 1 × 105 with ²/D = 1/30 I get f = 0.0601.
With ²/D = 1/1014 I get f = 0.0207. Note that given the values of ²/D, you might also
consider instead using D/² as your input variable.
b) Next, use your function to plot f versus Re for a given value of ²/D. That is, make your
own Moody chart. Cool! For the purpose of this question, write a script or function
where you loop over Re values over the range of 1 × 103 to 1 × 105 , for each Re use your
function to compute f for the case of ²/D = 0.1 and ²/D = 0.001, and plot.
In a Moody chart, a log-scale is used for both the x- and y-axis. To do this, replace
the plot command with the loglog command. The command loglog works exactly
the same as plot, only it uses log axes. Be sure to label your axes, label your data
sets, and print your figure to file. And if you would like to include a grid in your plot,
like an actual Moody chart, use the command grid on. After you plot, execute the
command grid on.
As a future refrence, if you wanted to make a semi-log plot where only one of the axes
uses a log-scale and the other uses a linear-scale, you can use the command semilogx
or semilogy. In the next chapter we will look at having vectors as inputs and outputs
in our functions, and will revisit this problem.
Chapter
6
Functions of vectors
In Chapter 6 we finish building our foundational knowledge of MATLAB with a
continued discussion of functions. By the end of this chapter you will be able to:
• Extend ability to create functions to vector inputs and outputs
• Demonstrate the ability to create vectorized functions
• Write functions with multiple/optional output variables
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
6.1 Functions and files
So far we have only put one function in each file. It is also possible to put more
than one function in a file, but only the first one, the top-level function can be
called from the Command Window . The other helper functions can be called from
anywhere inside the file, but not from any other file. Large programs almost
always require more than one function; keeping all the functions in one file
is convenient, but it makes debugging difficult because you can’t call helper
functions from the Command Window . To help with this problem, I often use the
top-level function to develop and test my helper functions.
For example, for Example 5.10 we wrote a function v_virial to compute
the compressibility (Z ) of a fluid using the truncated virial equation with corresponding states theory. To compute the molar volume (v) in cm3 /mol, we called a
second function v_ideal_gas to compute the molar volume of an ideal gas (v ig )
and then computed the molar volume as v = Z v ig . Both functions were stored
in separate files. Since every time I wish to estimate the molar volume using the
truncated virial I will need to calculate v ig , it would be convenient to keep both
functions in the same file. My final file might look as follows:
173
174
Chapter 6 Functions of vectors
Listing 6.1 v_virial.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
% res = v_virial(t,p_bar,tc,pc_bar,omega)
%
% Calculating the molar volume of a fluid in units of cm^3/mol using the
% truncated virial equation with correspondings states to estimate
% B. The user must specify the temperature (t) and critical temperature(tc)
% in units of K, the pressure (p) and critical pressure (pc) in units
% of bar, and the accentric factor (which is dimensionless).
% The function will then calculate the molar volume in units
% of cm^3/mol.
%
function res = v_virial( t,p_bar,tc,pc_bar,omega )
% First, we will want to work in SI units, so let's convert p_bar and
% pc_bar to Pa before we forget
p = p_bar*1e5;
pc = pc_bar*1e5;
% Molar gas constant in J/(mol K)
r = 8.314;
% Calculating the relative temperature, tr. It is important here that we
% are using absolute temperature units.
tr = t/tc;
%
% Now let's calculate B0 and B1 using the provided equations
b0 = 0.083-0.422/(tr^(1.6));
b1 = 0.139-0.172/(tr^(4.2));
% Now calculating B
b = r*tc/pc*(b0+omega*b1);
%
% Calculating the compressibility factor Z
z = 1 + b*p/(r*t);
%
% Calculating the ideal gas molar volume. We will use our function to do
% this which takes as input T in K and p in bar. It returns the molar
% volume in units of cm^3/mol, which is what we desire.
vig = v_ideal_gas(t,p_bar);
%
% Finally, calculating the molar volume of our fluiding in cm^3/mol using
% the truncated virial. Note that since Z is dimensionless, multiplying by
% the ideal gas volume in cm^3/mol gives the desired result.
res = z*vig;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% res = v_ideal_gas(t,p)
%
% Calculating the molar volume of a fluid in units of cm^3/mol using
% the ideal gas equation of state. The functiona call must pass
% the temperature (t) in units of K, and the pressure (p) in
% units of bar. The function will then calculate the molar volume in units
% of cm^3/mol.
%
function res = v_ideal_gas( t,p_bar )
% Converting the pressure from bar to Pa so that everything is in SI units
6.1 Functions and files
54
55
56
57
58
59
60
61
p = p_bar*1e5;
% The molar gas constant in units of J/(mol K), SI units
r = 8.314;
% With all SI units, computing the molar volume in m^3/mol
v = r*t/p;
% Converting the molar volume to cm^3/mol
res = v*(100^(3));
end
First, as mentioned earlier, helper functions can not be called from the
Command Window . Here is what would happen if I tried:
>> v_ideal_gas(440,60)
Undefined function or variable 'v_ideal_gas'.
The only function that MATLAB can see from the Command Window is the toplevel function. Trying instead
>> v_virial(440,60,425.125,37.960,0.201)
ans = 313.2382
It works as expected.
In writing v_virial for Exercise 5.10, we first wrote v_ideal_gas and tested
it from the Command Window to ensure it was working. We just saw that we can not
do that here. Here I might start by writing v_ideal_gas in two steps. First, I might
create a file v_virial.m and start with a top-level function named v_virial that
takes no input variables and returns no output value. I would then write a (helper)
function v_ideal_gas. My first test would be to pass a variable from v_virial
to v_ideal_gas and back to v_virial. The purpose of this is to make sure I have
my file set-up correctly and that the functions can properly communicate with
each other. This code might look like the following
1
2
3
4
function res = v_virial()
test_pass = 10
test_return = v_ideal_gas(test_pass)
end
5
6
7
8
9
function res = v_ideal_gas(r)
test_in = r
res = r;
end
Executing from the Command Window
175
176
Chapter 6 Functions of vectors
>> v_virial
test_pass = 10
test_in = 10
test_return = 10
Nice! Next, I would pass from v_virial a value of the temperature and pressure
to v_ideal_gas and compute v ig to check for correctness, just as we did before
from the Command Window .
1
2
3
function res = v_virial ()
test = v_ideal_gas(440,60)
end
4
5
6
7
8
9
10
function res = v_ideal_gas(t,p_bar)
p = p_bar*1e5;
r = 8.314;
v = r*t/p;
res = v*(100^(3));
end
From the Command Window
>> v_virial
test = 609.6933
Perfect! Now we are confident v_ideal_gas is bug free. So next if we move on
and write v_virial, we know any bugs are in v_virial and not elsewhere.
I might next write v_virial in two step. First, I can calculate Z with all of
my parameters specified within v_virial. This way I can localize any errors to
v_virial and not due to an issue passing variables.
6.2 Vectors as input variables
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function res = v_virial()
% parameters
t = 440;
p_bar = 60;
tc = 425.15;
pc_bar = 37.960;
omega = 0.201;
%
p = p_bar*1e5;
pc = pc_bar*1e5;
r = 8.314;
tr = t/tc;
b0 = 0.083-0.422/(tr^(1.6));
b1 = 0.139-0.172/(tr^(4.2));
b = r*tc/pc*(b0+omega*b1);
z = 1 + b*p/(r*t);
vig = v_ideal_gas(t,p_bar);
res = z*vig;
end
20
21
22
23
24
25
26
function res = v_ideal_gas(t,p_bar)
p = p_bar*1e5;
r = 8.314;
v = r*t/p;
res = v*(100^(3));
end
Notice that I have left my comments out here to save space. Running from the
Command Window :
>> v_virial
ans = 313.2382
Last, we would pass the parameters to the function, which would bring us
back to our final code. This code was relatively simple. If you had a larger function,
you might build it up in smaller, testable parts.
For this problem I need only two functions, but if there were more, I could
write and test them one at a time, and then combine them into a working program.
6.2 Vectors as input variables
Since many of the built-in functions take vectors as arguments, it should come as
no surprise that you can write functions that take vectors. Here’s a simple (silly)
177
178
Chapter 6 Functions of vectors
example:
1
2
3
function res = display_vector(X)
X
end
There’s nothing special about this function at all. The only difference from
the scalar functions we’ve seen is that I used a capital letter to remind me that X is
a vector.
This is another example of a function that doesn’t actually have a return value;
it just displays the value of the input variable:
>> display_vector(1:3)
X = 1
2
3
Here’s a more interesting example that encapsulates the code from Section 4.15
(page 102) that adds up the elements of a vector:
Listing 6.2 mysum.m
1
2
3
4
5
6
7
function res = mysum(X)
total = 0;
for i=1:length(X)
total = total + X(i);
end
res = total;
end
I called it mysum to avoid a collision with the built-in function sum, which does
pretty much the same thing.
Here’s how you call it from the Command Window :
>> total = mysum(1:3)
total = 6
Because this function has a return value, I made a point of assigning it to a
variable.
6.3 Vectors as output variables
There’s also nothing wrong with assigning a vector to an output variable. Here’s
an example that encapsulates the code from Section 4.16 (page 103):
6.3 Vectors as output variables
Listing 6.3 myapply.m
1
2
3
4
5
6
7
8
function res = myapply(X)
% Pre-size Y, the vector containing the result
Y = zeros(1,length(X));
for i=1:length(X)
Y(i) = X(i)^2;
end
res = Y;
end
Ideally I would have changed the name of the output variable to Res, as a reminder
that it is supposed to get a vector value, but I didn’t.
Here’s how myapply works:
>> V = myapply(1:3)
V = 1
4
9
Example 6.1
Write a function named find_negative_element that encapsulates the code,
from Example 4.9, that finds the location of (or index of) the first negative number
in a vector.
Solution: (Link to screen cast and accompanying M-files.)
Listing 6.4 find_negative_element.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%
%
%
%
%
%
%
function res = find_negative_element(X)
Function to find the index of the first negative
number in vector X. An answer will only be
returned to the Command Window if a negative
number is found. This is an improvement over
the previous version of our code.
function res = find_negative_element(X)
for i=1:length(X)
if X(i) < 0
res = i;
break
end
end
end
Note that if you desired, if there is no negative element, you could have MATLAB
throw an error message with the command, error.
179
180
Chapter 6 Functions of vectors
6.4 Multiple/optional output variables
When we first introduced functions in Section 5.2, we mentioned that you could
create a new M-file in MATLAB’s Editor by using the New button, then select
Function from the dropdown menu. If done this way, MATLAB will provide the
following template for a new function.
Listing 6.5 untitled.m
1
2
3
function [ output_args ] = untitled( input_args )
%UNTITLED Summary of this function goes here
%
Detailed explanation goes here
4
5
6
end
We notice two differences. First, the documentation is placed under the function
signature. Recall, in scripts, we always put the documentation at the top of the
file. For functions, you can add the documentation either immediately above or
below the function signature. Either will work and provide the same output when
you use help or doc. My preference is to put the documentation at the top. This
way I am using the same convention for both scripts and functions, and the top
of the file is where I am accustomed to looking for this information.
The second difference is the output variable is placed in brackets. At first
glance, after our previous discussion, it may appear that MATLAB is packing
multiple variables up into a vector to be returned. But there is more than meets
the eye. We can in fact place multiple variables separated by a comma in between
the brackets to return multiple variables. However, whether or not more than one
variable is actually returned is dependent on how the function is called. Whether
subsequent variables (the variables after the first) are actually returned is optional,
although they do need to be assigned within the function. Let’s take a look at
an example to see what I mean. I will work with our truncated virial equation
function, Listing 6.1, which we are already familiar with. In the interest of space,
I will remove all of the documentation. I will also keep appending the name of
the top function and the file so that I can save a unique copy of each iteration.
Let’s start by taking the output variable (res) in the function signature of the top
function (v_virial) and put it in brackets.
6.4 Multiple/optional output variables
Listing 6.6 v_virial_1.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function [res] = v_virial_1( t,p_bar,tc,pc_bar,omega )
% First, we will want to work in SI units, so let's convert p_bar and
% pc_bar to Pa before we forget
p = p_bar*1e5;
pc = pc_bar*1e5;
% Molar gas constant in J/(mol K)
r = 8.314;
% Calculating the relative temperature, tr. It is important here that we
% are using absolute temperature units.
tr = t/tc;
%
% Now let's calculate B0 and B1 using the provided equations
b0 = 0.083-0.422/(tr^(1.6));
b1 = 0.139-0.172/(tr^(4.2));
% Now calculating B
b = r*tc/pc*(b0+omega*b1);
%
% Calculating the compressibility factor Z
z = 1 + b*p/(r*t);
%
% Calculating the ideal gas molar volume. We will use our function to do
% this which takes as input T in K and p in bar. It returns the molar
% volume in units of cm^3/mol, which is what we desire.
vig = v_ideal_gas(t,p_bar);
%
% Finally, calculating the molar volume of our fluiding in cm^3/mol using
% the truncated virial. Note that since Z is dimensionless, multiplying by
% the ideal gas volume in cm^3/mol gives the desired result.
res = z*vig;
end
function res = v_ideal_gas( t,p_bar )
% Converting the pressure from bar to Pa so that everything is in SI units
p = p_bar*1e5;
% The molar gas constant in units of J/(mol K), SI units
r = 8.314;
% With all SI units, computing the molar volume in m^3/mol
v = r*t/p;
% Converting the molar volume to cm^3/mol
res = v*(100^(3));
end
Let’s confirm that we get the same answer as before:
>> v_virial_1(440,60,425.125,37.960,0.201)
ans = 313.2382
Nice! Next, imagine you might also wish to return the computed compressibility,
z, and the ideal gas molar volume, vig. We will add both to the brackets, separating the variables by a comma.
181
182
Chapter 6 Functions of vectors
Listing 6.7 v_virial_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function [res,z,vig] = v_virial_2( t,p_bar,tc,pc_bar,omega )
% First, we will want to work in SI units, so let's convert p_bar and
% pc_bar to Pa before we forget
p = p_bar*1e5;
pc = pc_bar*1e5;
% Molar gas constant in J/(mol K)
r = 8.314;
% Calculating the relative temperature, tr. It is important here that we
% are using absolute temperature units.
tr = t/tc;
%
% Now let's calculate B0 and B1 using the provided equations
b0 = 0.083-0.422/(tr^(1.6));
b1 = 0.139-0.172/(tr^(4.2));
% Now calculating B
b = r*tc/pc*(b0+omega*b1);
%
% Calculating the compressibility factor Z
z = 1 + b*p/(r*t);
%
% Calculating the ideal gas molar volume. We will use our function to do
% this which takes as input T in K and p in bar. It returns the molar
% volume in units of cm^3/mol, which is what we desire.
vig = v_ideal_gas(t,p_bar);
%
% Finally, calculating the molar volume of our fluiding in cm^3/mol using
% the truncated virial. Note that since Z is dimensionless, multiplying by
% the ideal gas volume in cm^3/mol gives the desired result.
res = z*vig;
end
function res = v_ideal_gas( t,p_bar )
% Converting the pressure from bar to Pa so that everything is in SI units
p = p_bar*1e5;
% The molar gas constant in units of J/(mol K), SI units
r = 8.314;
% With all SI units, computing the molar volume in m^3/mol
v = r*t/p;
% Converting the molar volume to cm^3/mol
res = v*(100^(3));
end
Let’s try running as before, and let’s also try assigning the output to a variable:
>> v_virial_2(440,60,425.125,37.960,0.201)
ans = 313.2382
>> v = v_virial_2(440,60,425.125,37.960,0.201)
v = 313.2382
6.5 Vectorizing your functions
Hey, what gives! The compressibility and ideal gas molar volume are not returned.
The issue is we need to tell MATLAB to return them. Here’s how. First we will
return none, then one and both of the optional variables.
>> v = v_virial_2(440,60,425.125,37.960,0.201)
v = 313.2382
>> [v,z] = v_virial_2(440,60,425.125,37.960,0.201)
v = 313.2382
z = 0.5138
>> [v,z,vig] = v_virial_2(440,60,425.125,37.960,0.201)
v = 313.2382
z = 0.5138
vig = 609.6933
Cool! We see that what is returned in not a vector, but instead each is returned as
its own variable.
You will find that I do not always utilize this feature of MATLAB when I wish
to return multiple scalar variables. Many times if I wish to return multiple scalar
variables, I will often pack them up to a single vector and return it. This way I am
certain that I am returning all of the variables regardless of how I call the function.
This is just my preference and style. You are free to disagree. Just know that there
are some cases where you are not free to choose, such as when we will create
function files to be used by fsolve and ode45. Also, know that this feature is
powerful in that it can allow you to return variables of different sizes and types,
where attempting to pack them up to a single vector or matrix would fail. Last,
know that when we call fzero, fsolve and ode45, they were written to return
optional variables as done here.
6.5 Vectorizing your functions
Functions that work on vectors will almost always work on scalars as well, because
MATLAB considers a scalar to be a vector with length 1.
>> mysum(17)
ans = 17
>> myapply(9)
ans = 81
Unfortunately, the converse is not always true. If you write a function with
scalar inputs in mind, it might not work on vectors. But it might! If the operators
and functions you use in the body of your function work on vectors, then your
183
184
Chapter 6 Functions of vectors
function will probably work on vectors.
For example, here is the very first function we wrote:
1
2
3
4
5
function res = myfunc (x)
s = sin(x)
c = cos(x)
res = abs(s) + abs(c)
end
And lo! It turns out to work on vectors:
>> Y = myfunc(1:3)
Y = 1.3818
1.3254
1.1311
At this point, I want to take a minute to acknowledge that I have been a little
harsh in my presentation of MATLAB, because there are a number of features that
I think make life harder than it needs to be for beginners. But here, finally, we
are seeing features that show MATLAB’s strengths. MATLAB is extremely good
with matrix (and vector) operations. In fact, MATLAB’s name comes from MAtrix
LABoratory. We started the text working with scalars and for loops because I
assumed most of you had no prior programming experience. Many students
initially find thinking like a computer to be challenging, let alone thinking about
vector operations. You can always use for loops, and when in doubt, it is good
to go this route. But matrix (or vector) operations are much more efficient (both
in terms of execution time and the resulting compactness of your code) and can
make your code much easier to read.
Some of the other functions we wrote don’t work on vectors, but they can
be patched up with just a little effort. For example, here’s hypotenuse from Section 5.5 (and Listing 5.9):
1
2
3
function res = hypotenuse(a, b)
res = sqrt(a^2 + b^2);
end
This doesn’t work on vectors because the ^ operator tries to do matrix exponentiation, which only works on square matrices.
>> hypotenuse(1:3, 1:3)
Error using ==> mpower
Matrix must be square.
But if you replace ^ with the elementwise operator .^, it works!
6.6 Sums and differences
185
Listing 6.8 hypotenuse.m
1
2
3
function res = hypotenuse(a, b)
res = sqrt(a.^2 + b.^2);
end
>> A = [3,5,8];
>> B = [4,12,15];
>> C = hypotenuse(A, B)
C = 5
13
17
In this case, it matches up corresponding elements from the two input vectors,
so the elements of C are the hypotenuses of the pairs (3, 4), (5, 12) and (8, 15),
respectively.
In general, if you write a function using only elementwise operators and
the function works on vectors, then the new function will also work on scalars.
Remember, to MATLAB, everything is a matrix.
6.6 Sums and differences
Another common vector operation is cumulative sum, which takes a vector as
an input and computes a new vector that contains all of the partial sums of the
original. In math notation, if V is the original vector, then the elements of the
cumulative sum, C , are:
Ci =
i
X
Vj
j =1
In other words, the i th element of C is the sum of the first i elements from V .
MATLAB provides a function named cumsum that computes cumulative sums:
>> V = 1:5
V = 1
2
3
4
5
>> C = cumsum(V)
C = 1
3
6
10
15
Example 6.2
Write a function named cumulative_sum that uses a loop to compute the cumulative sum of the input vector.
As we have seen before, when learning a new skill, writing our own functions to
perform the same operations as a built-in function is excellent practice as we can
quickly check if we get the correct answer.
186
Chapter 6 Functions of vectors
Solution: (Link to screen cast and accompanying M-files.)
Listing 6.9 cumulative_sum.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% function res = cumulative_sum(X)
%
% Function to compute the cumulative sum of vector X which
% is returned as res.
%
function res = cumulative_sum(X)
% Y will be our vector to store the cumulative sum.
% Initialize it with values of zero since we will be
% performing a summation
Y = zeros(1,length(X));
for i=1:length(X)
for j=1:i
Y(i) = Y(i) + X(j);
end
end
res = Y;
end
>> V = 1:5
V = 1
2
3
4
>> C = cumulative_sum(V)
C = 1
3
6
10
5
15
This agrees exactly with cumsum.
The inverse operation of cumsum is diff, which computes the difference between successive elements of the input vector.
>> D = diff(C)
D = 2
3
4
5
Notice that the output vector is shorter by one than the input vector. As a
result, MATLAB’s version of diff is not exactly the inverse of cumsum. If it were,
then we would expect cumsum(diff(X)) to be X:
>> cumsum(diff(V))
ans = 1
2
3
But it isn’t.
4
6.7 Products and ratios
187
Example 6.3
Write a function named mydiff that computes the inverse of cumsum, so that
cumsum(mydiff(X)) and mydiff(cumsum(X)) both return X.
Solution:
Listing 6.10 mydiff.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% function res = mydiff(X)
%
% Function to compute the inverse of the cumulative sum,
% which is returned as res.
%
function res = mydiff(X)
% Y will be our vector to store the inverse cumulative sum.
% Initialize it with values of X.
Y = X;
for i=2:length(X)
Y(i) = Y(i)-X(i-1);
end
res = Y;
end
>> V = 1:5
V = 1
2
3
4
5
>> C = cumulative_sum(V)
C = 1
3
6
10
>> VI = mydiff(C)
VI = 1
2
3
15
4
5
6.7 Products and ratios
The multiplicative version of cumsum is cumprod, which computes the cumulative
product. In math notation, that’s:
Pi =
i
Y
j =1
In MATLAB, that looks like:
>> V = 1:5
V = 1
2
3
4
5
Vj
188
Chapter 6 Functions of vectors
>> P = cumprod(V)
P = 1
2
6
24
120
Example 6.4
Write a function named cumulative_product that uses a loop to compute the
cumulative product of the input vector.
Solution: (Link to screen cast and accompanying M-files.)
Listing 6.11 cumulative_product.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
% function res = cumulative_product(X)
%
% Function to compute the cumulative product of vector X which
% is returned as res.
%
function res = cumulative_product(X)
% Y will be our vector to store the cumulative product.
% Initialize it with values of one since we will be
% performing a continuous product.
Y = ones(1,length(X));
for i=1:length(X)
for j=1:i
Y(i) = Y(i)*X(j);
end
end
res = Y;
end
>> V = 1:5
V = 1
2
3
4
5
>> P = cumulative_product(V)
P = 1
2
6
24
120
A perfect match to cumprod.
MATLAB doesn’t provide the multiplicative version of diff, which would be
called ratio, and which would compute the ratio of successive elements of the
input vector.
6.7 Products and ratios
189
Example 6.5
Write a function named myratio that computes the inverse of cumprod, so that
cumprod(myratio(X)) and myratio(cumprod(X)) both return X.
You can use a loop, or if you want to be clever, you can take advantage of the fact
that e ln a+ln b = ab.
If you apply myratio to a vector that contains Fibonacci numbers, you can
p confirm
that the ratio of successive elements converges on the golden ratio, (1 + 5)/2 (see
Example 4.6 on page 108).
Solution:
Listing 6.12 myratio.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% function res = myratio(X)
%
% Function to compute the inverse of the continuous product,
% which is returned as res.
%
function res = myratio(X)
% Y will be our vector to store the inverse continous product.
% Initialize it with values of X.
Y = X;
for i=2:length(X)
Y(i) = Y(i)/X(i-1);
end
res = Y;
end
>> V = 1:5
V = 1
2
3
4
5
>> P = cumulative_product(V)
P = 1
2
6
24
120
>> VI = myratio(P)
VI = 1
2
3
4
5
190
Chapter 6 Functions of vectors
Listing 6.13 myratio_clever.m
1
2
3
4
5
6
7
8
9
10
% function res = myratio_clever(X)
%
% Function to compute the inverse of the continuous product,
% which is returned as res.
%
function res = myratio_clever(X)
Y = log(X);
Z = mydiff(Y);
res = exp(Z);
end
>> VI = myratio_clever(P)
VI = 1
2
3
4
5
6.8 Existential quantification
It is often useful to check the elements of a vector to see if there are any that
satisfy a condition. For example, you might want to know if there are any positive
elements. In logic, this condition is called existential quantification, and it is
denoted with the symbol ∃, which is pronounced “there exists.” For example, this
expression
∃x in S : x > 0
means, “there exists some element x in the set S such that x > 0.” In MATLAB it is
natural to express this idea with a logical function, like exists, that returns 1 if
there is such an element and 0 if there is not.
Listing 6.14 exists.m
1
2
3
4
5
6
7
8
9
function res = exists(X)
for i=1:length(X)
if X(i) > 0
res = 1;
return
end
end
res = 0;
end
We haven’t seen the return statement before; it is similar to break except
that it breaks out of the whole function, not just the loop. That behavior is what
we want here because as soon as we find a positive element, we know the answer
6.9 Universal quantification
191
(it exists!) and we can end the function immediately without looking at the rest of
the elements.
If we exit at the end of the loop, that means we didn’t find what we were
looking for (because if we had, we would have hit the return statement).
This isn’t the only way, although it was a nice way to introduce return. I could
accomplish the same result with the following function
Listing 6.15 exists_2.m
1
2
3
4
5
6
7
8
9
function res = exists_2(X)
res = 0;
for i=1:length(X)
if X(i) > 0
res = 1;
break
end
end
end
In this case, I initialize res with a value of 0 (false). If I find an element that is
positive, then there exists at least one element that is positive and I change the
value of res to 1 (true). Once I have found a positive element, there is no need to
continue searching since I only asked if there was at least one positive element. I
therefore add break which exits us out of the loop and hence the function. Note
that if you didn’t include break your function would still work and return the
correct result. You will just make the computer work harder than it needs to,
which is matter of efficiency.
6.9 Universal quantification
Another common operation on vectors is to check whether all of the elements
satisfy a condition, which is known to logicians as universal quantification and
denoted with the symbol ∀ which is pronounced “for all.” So this expression
∀x in S : x > 0
means “for all elements, x, in the set S, x > 0.”
A slightly silly way to evaluate this expression in MATLAB is to count the
number of elements that satisfy the condition. A better way is to reduce the
problem to existential quantification; that is, to rewrite
∀x in S : x > 0
as
192
Chapter 6 Functions of vectors
∼ ∃x in S : x ≤ 0
Where ∼ ∃ means “does not exist.” In other words, checking that all the elements are positive is the same as checking that there are no elements that are
non-positive.
Example 6.6
Write a function named forall that takes a vector and returns 1 if all of the
elements are positive and 0 if there are any non-positive elements.
Solution: (Link to screen cast and accompanying M-file.)
Listing 6.16 forall.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% function res = forall(X)
%
% A function to check if all of the elements
% of X are positive. We know that if at least
% one of the elements is negative, then they
% are not all positive. So we check this
% simpler case.
%
% Return 1 for true (all positive), 0 for false
%
function res = forall(X)
res = 1;
for i = 1:length(X)
if X(i) < 0
res = 0;
return
end
end
end
Note that here I use return which exits us out of the function, but break in this
case would cause the same effect.
6.10 Logical vectors
When you apply a logical operator to a vector, the result is a logical vector; that is,
a vector whose elements are the logical values 1 and 0.
>> V = -3:3
V = -3
-2
-1
0
1
2
3
6.10 Logical vectors
>> L = V>0
L = 0
0
193
0
0
1
1
1
You can think of logical operators as acting elementwise. In this example, L is a
logical vector whose elements correspond to the logical comparison of each of the
corresponding elements of V. For each positive element of V, the corresponding
element of L is 1, “true”.
Logical vectors can be used like flags to store the state of a condition. They are
frequently used to extract the elements of a vector or matrix that are “true” (i.e.,
that satisfy a specific condition). They are also often used with the find function,
which takes a logical vector and returns a vector that contains the indices of the
elements that are “true.”
Applying find to L yields
>> find(L)
ans = 5
6
7
which indicates that elements 5, 6 and 7 have the value 1. (Remember, 1 corresponds to “true”.)
If there are no “true” elements, the result is an empty vector.
>> find(V>10)
ans = Empty matrix: 1-by-0
This example computes the logical vector and passes it as an argument to find
without assigning it to an intermediate variable. You can read this version abstractly as “find the indices of elements of V that are greater than 10.”
We can also use find to write exists more concisely:
1
2
3
4
function res = exists(X)
L = find(X>0)
res = length(L) > 0
end
This could also be achieved using a logical vector:
1
2
3
4
5
6
function res = exists(X)
res = 1; % Start by assuming true
if sum(X>0) == 0 % Check if every element is negative
res = 0;
end
end
194
Chapter 6 Functions of vectors
And here is an even more compact solution:
1
2
3
function res = exists(X)
res = sum(X>0) ~= 0;
end
Example 6.7
Write a version of forall using find.
Solution: (Link to screen cast and accompanying M-file.)
Listing 6.17 forall_find.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
% function res = forall_find(X)
%
% A function to check if all of the elements
% of X are positive. We know that if at least
% one of the elements is negative, then they
% are not all positive. So we check this
% simpler case.
%
% Return 1 for true (all positive), 0 for false
%
function res = forall_find(X)
L = find(X<0);
res = length(L) == 0;
end
In writing the solution, MATLAB suggests that isempty would be more efficient
than checking if a vector has a length of zero. After consulting MATLAB’s documentation, here is what the updated function would look like:
6.10 Logical vectors
195
Listing 6.18 forall_find_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
% function res = forall_find_2(X)
%
% A function to check if all of the elements
% of X are positive. We know that if at least
% one of the elements is negative, then they
% are not all positive. So we check this
% simpler case.
%
% Return 1 for true (all positive), 0 for false
%
function res = forall_find_2(X)
L = find(X<0);
res = isempty(L);
end
And in the interest of exploring alternatives, we could use a logical vector instead
of find.
Listing 6.19 forall_logical.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
% function res = forall_logical(X)
%
% A function to check if all of the elements
% of X are positive. We know that if at least
% one of the elements is negative, then they
% are not all positive. So we check this
% simpler case.
%
% Return 1 for true (all positive), 0 for false
%
function res = forall_logical(X)
L = X<0;
res = sum(L)==0;
end
Not to be out done, we can use the MATLAB function any in the following version,
where here we use ∼, the logical “not” expression.
196
Chapter 6 Functions of vectors
Listing 6.20 forall_logical_any.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
% function res = forall_logical_any(X)
%
% A function to check if all of the elements
% of X are positive. We know that if at least
% one of the elements is negative, then they
% are not all positive. So we check this
% simpler case.
%
% Return 1 for true (all positive), 0 for false
%
function res = forall_logical_any(X)
L = X<0;
res = ~any(L);
end
And how did I come up with the idea of using any? MATLAB suggested its use as
a comment in forall_find_2.
Recall our discussion of vector indexing errors in Section 4.10 on page 92
when we first discussed vectors. We found that we could refer to elements of a
vector by either their integer index or their logical value. At that point we said we
would discuss the use of logicals later. Well, what better time than now! It is best
to see how we might do this using an example. Imagine we again have the vector V:
>> V = -3:3
V = -3
-2
-1
0
1
2
3
Using the find command before, we found that elements 5, 6, and 7 were greater
than 0. Now imagine if the value of an element is greater than 0, we want to assign
it a value of 10. We can do this two ways. First, let’s use integer indices:
>> integer_index = find(V>0)
ans = 5
6
7
>> V(integer_index) = 10
V = -3
-2
-1
0
10
10
10
And if you had wanted to, the two statements could be combined as V(find(V>0))
= 10. Next, let’s use logical values:
>> V = -3:3;
>> logical_index = V>0
logical_index = 0
0
0
0
1
1
1
6.11 CPB Examples
197
>> V(logical_index) = 10
V = -3
-2
-1
0
10
10
10
Once again, the two statements could be combined as V(V>0)=10.
While here the use of find and a logical vector were equivalent to identify
elements of vector V satisfying a given condition, Know that the use of a logical
vector is preferred.
In addition to an apply operation as was done here, we will frequently want to
isolate specific elements from a vector or matrix satisfying a given condition. Let’s
consider the same vector V, but know we want assign the elements of V greater
than 0 to a new vector VP:
>> V = -3:3;
>> VP = V(V>0)
VP =
1
2
3
While this example is rather trivial, we will find it much more useful in some of
our future examples.
6.11 CPB Examples
Example 6.8
While I am certain at this point you are tired of the Antoine equation, let’s again
revisit our old friend.
a) In Example 5.8 part b) we wrote a beautiful function to compute p sat where the
user input A, B , C , the desired temperature, and the desired units. Vectorize the
function so that you can pass a vector of temperatures and return a vector of
values of p sat . To test your new function, compare to your result for Example 5.8
part c).
b) With your working function from a), test the use of the built-in find function. When computing p sat using the Antoine equation we provide a range of
temperatures, unaware beforehand what the corresponding values of p sat are.
We may desire only values of p sat below a certain value. Try using find as a
filter for this particular case. That is, check your returned p sat vector for values
less than some specified value. Then save these values and the corresponding
temperature to a pair of new vectors. Depending on the range of temperatures
you are using, try 0.5 times p sat at the highest temperature.
c) In b) you used the find function. Could you instead use a logical vector?
Remember, the use of a logical vector is preferred. The use of find in b) is an
exercise to get you accustomed to using the find command.
198
Chapter 6 Functions of vectors
Solution: Antoine equation fun time!
a) Okay, we already have a working function. Here we just want to vectorize it
so that we can input a vector of temperatures and compute/return a vector of
vapor pressures. All that we mean by vectorize is we need to change our *, /,
and ˆ operators to their elementwise equivalent by adding a period: .*, ./, and
.ˆ. The updated function is provided below:
Listing 6.21 antoine_p_units.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
% res = antoine_p_units( a,b,c,tC,units )
%
% Computing the vapor prssure of a fluid using Antoine's equation. The user
% must provide Antoine parameters A, B, and C. I will assume they are from
% the same source as the ethanol parameters provided in class, so the
% default units will be t in degress C and p in mmHg. Pleasue be sure to
% check this for future cases. The user must also provide the temperature
% in degrees C (tC). This will result in a pressure in mmHg.
% In this version of the code, the user must also provide a value for
% units, which will indicate the desired units for the final reported
% pressure. The value of units is as follows:
%
units = 1 for mmHg
%
= 2 for atm
%
= 3 for kPa
%
= 4 for bar
%
function res = antoine_p_units( a,b,c,tC,units )
% First, call antoine_p to compute p in mmHg
logp_mmHg = a-b./(tC+c);
p_mmHg = 10.^(logp_mmHg);
% Checking for desired units
if units == 1 % mmHg
res = p_mmHg;
elseif units == 2 % atm
res = p_mmHg./760;
elseif units == 3 % kPa
res = p_mmHg.*101.325./760;
elseif units == 4 % bar
res = p_mmHg.*101.325./760./100;
else
disp('Invalid choice of units. p is returned in mmHg')
res = p_mmHg;
end
end
To test its use, let’s compute vapor pressure in bar over the range –114.1 to 243.1
◦
C, the entire range of applicability of our Antoine equation for ethanol. In the
interest of space, we will only use a small number of temperatures.
>>
>>
>>
>>
a = 8.13484;
b = 1662.48;
c = 238.141;
tC = -114.1:30:243.1
6.11 CPB Examples
tC =
Columns 1
-114.1000
Columns 9
125.9000
199
through 8
-84.1000 -54.1000
through 12
155.9000 185.9000
-24.1000
5.9000
35.9000
65.9000
95.9000
0.0280
0.1560
0.6192
1.9181
0.6192
1.9181
215.9000
>> p_bar = antoine_p_units(a,b,c,tC,4)
Columns 1 through 8
0.0000
0.0000
0.0002
0.0031
Columns 9 through 12
4.9318
10.9822
21.8363
39.6482
Nice! Know that the first two pressures are not 0. This is just an issue with
MATLAB’s chosen format.
>> p_bar(1)
ans = 7.1958e-09
b) Now we will use the find command to create a new vector with just the values
of p sat less than 0.5p sat at the highest T (since p sat increases with T ).
>> psat_max = p_bar(length(tC))
psat_max = 39.648
>> index = find(p_bar < 0.5*psat_max)
index =
1
2
3
4
5
6
7
>> p_trim = p_bar(index)
p_trim =
Columns 1 through 8
0.0000
0.0000
0.0002
Columns 9 through 10
4.9318
10.9822
8
0.0031
9
10
0.0280
0.1560
Also, know that instead of psat_max = p_bar(length(tC)) we could equivalently use psat_max = p_bar(end). end is a special keyword for the last
element index.
c) Besides find, we could accomplish the same result using a logical comparison.
This is what MATLAB would actually prefer. After all, find is undoubtedly performing a logical comparison to determine for which elements the statement
is true and false.
>> index_l = p_bar < 0.5*psat_max
index_l =
1
1
1
1
1
1
1
1
>> p_trim_l = p_bar(index_l)
p_trim =
Columns 1 through 6
1
1
0
0
200
Chapter 6 Functions of vectors
0.0000
0.0000
0.0002
Columns 7 through 10
0.6192
1.9181
4.9318
0.0031
0.0280
0.1560
10.9822
6.12 Glossary
top-level function: The first function in an M-file; it is the only function that can
be called from the Command Window or from another file.
helper function: A function in an M-file that is not the top-level function; it only
be called from another function in the same file.
existential quantification: A logical condition that expresses the idea that “there
exists” an element of a set with a certain property.
universal quantification: A logical condition that expresses the idea that all elements of a set have a certain property.
logical vector: A vector, usually the result of applying a logical operator to a
vector, that contains logical values 1 and 0.
6.13 Exercises
6.13 Exercises
Exercise 6.1 In Example 5.6 we used several functions stored in separate M-files to
compute the first n Fibonacci numbers, and then checked whether this formula produced
a Pythagorean triple for each number in the sequence. Combine all of these functions into
a single file where fib_triple.m is the top-level file. Check the function for correctness
by comparing to your results from Example 5.6. Note, do not forget about the M-file
hypotenuse, Listing 5.9.
Exercise 6.2 Revisit and modify find_negative_element from Example 6.1 to use the
find command to return the location (or index) of the first negative element in a vector.
Exercise 6.3 Let’s continue to build upon our solution to Exercises 2.1, 3.1 and 4.1. Specifically, encapsulate your script from Exercise 4.1 to compute the surface tension of a fluid.
Your function should take as inputs the model parameters and the temperature. Make
sure the function is vectorized so that it can take as an input a vector of temperatures and
return a vector of surface tensions.
To confirm the correctness of your code, compute the surface tension over the entire
temperature range for which the expression is applicable for propane, ethanol, and
acetone. Compare to your results from Exercise 4.1.
Exercise 6.4 In Example 5.10 we used the ideal gas equation of state and the truncated
virial equation with corresponding states theory to estimate the molar volume of n-butane
at 350 K and 2 bar. Using the truncated virial equation, we computed the compressibility,
Z , and then calculated the molar volume as v = Z v ig , where v ig is the molar volume of
an ideal gas at the same temperature and pressure.
Since our function for the truncated virial (v_virial) is dependent on our function
for the molar volume of an ideal gas (v_ideal_gas), in Section 6.1 we decided to combine
the functions into a single file, where v_virial is the top function and v_ideal_gas is
the helper function. Let’s continue to build upon that function here. Recall that from the
NIST WebBook1 for n-butane we have: Tc = 425.125 K, p c = 37.960 bar, and ω = 0.201,
a) First, let’s vectorize v_virial and v_ideal_gas. By vectorize, you should be able to
pass a vector of temperatures and/or a vector of pressures and compute a vector of
molar volumes.
b) Test your function by computing the molar volume at a constant pressure of 2 bar
over the temperature range 350 to 450 K. How does the molar volume change with
respect to temperature at constant pressure?
c) Next, update your function so that it additionally returns the compressibility, Z . Following the last question, how does Z change with respect to temperature at constant
pressure?
Exercise 6.5 Let’s build upon your friction factor code from Exercise 5.2, specifically your
Moody chart. Vectorize your function and eliminate the for loop. As a hint, remember
your work from Exercise 4.4.
1 http://webbook.nist.gov/chemistry/fluid/
201
Chapter
7
Zero-finding
In Chapter 7 we begin to explore MATLAB’s numerical methods, starting with
zero-finding and roots of polynomials. By the end of this chapter you will be able
to:
• Explain how MATLAB’s zero-finding algorithms work
• Exhibit ability to construct “error” functions for use with fzero
• Make use of fzero to find zero’s of an equation
• Apply roots to obtain all of the roots of a cubic equation of state
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
7.1 Why functions?
Chapter 5 explained some of the benefits of functions, including
• Each function has its own workspace, so using functions helps avoid name
collisions.
• Functions lend themselves to incremental development: you can debug
the body of the function first (as a script), then encapsulate it as a function,
and then generalize it by adding input variables.
• Functions allow you to divide a large problem into small pieces, work on
the pieces one at a time, and then assemble a complete solution.
• Once you have a function working, you can forget about the details of how
it works and concentrate on what it does. This process of abstraction is an
important tool for managing the complexity of large programs.
203
204
Chapter 7 Zero-finding
Another reason you should consider using functions is that many of the tools
provided by MATLAB require you to write functions. For example, in this chapter
we will use fzero to find solutions of nonlinear equations. In the next chapter
we will use fsolve to solve systems of nonlinear equations. And later we will use
ode45 to approximate solutions to differential equations. There are many other
cases, but there is only so much class time.
7.2 Maps
In mathematics, a map is a correspondence between one set called the range and
another set called the domain. For each element of the range, the map specifies
the corresponding element of the domain.
You can think of a sequence as a map from positive integers to elements. You
can think of a vector as a map from indices to elements. In these cases the maps
are discrete because the elements of the range are countable.
You can also think of a function as a map from inputs to outputs, but in this
case the range is continuous because the inputs can take any value, not just
integers. (Strictly speaking, the set of floating-point numbers is discrete, but since
floating-point numbers are meant to represent real numbers, we think of them as
continuous.)
7.3 A note on notation
In this chapter I need to start talking about mathematical functions, and I am
going to use a notation you might not have seen before.
If you have studied functions in a math class, you have probably seen something like
f (x) = x 2 − 2x − 3
which is supposed to mean that f is a function that maps from x to x 2 − 2x − 3.
The problem is that f (x) is also used to mean the value of f that corresponds to a
particular value of x. To make clear what this means, here I will instead use arrow
notation
f : x → x 2 − 2x − 3
which means “ f is the function that maps from x to x 2 − 2x − 3.” In MATLAB, this
would be expressed like this:
Listing 7.1 error_func.m
1
2
3
function res = error_func(x)
res = x.^2 - 2.*x - 3;
end
7.4 Nonlinear equations
I’ll explain soon why this function is called error_func. Also note that I have
vectorized the function; it will work regardless if x is a scalar or vector. Vectorizing
your functions is in general a good practice. Now, back to our regularly-scheduled
programming.
7.4 Nonlinear equations
What does it mean to “solve” an equation? That may seem like an obvious question, but I want to take a minute to think about it, starting with a simple example:
let’s say that we want to know the value of a variable, x, but all we know about it
is the relationship x 2 = a. If you have taken algebra, you probably know how to
p
“solve” this equation: you take the square root of both sides and get x = a. Then,
with the satisfaction of a job well done, you move on to the next problem.
But what have you really done? The relationship you derived is equivalent
to the relationship you started with—they contain the same information about
x—so why is the second one preferable to the first?
There are two reasons. One is that the relationship is now “explicit in x;”
because x is all alone on the left side, we can treat the right side as a recipe for
computing x, assuming that we know the value of a. The other reason is that the
recipe is written in terms of operations we know how to perform. Assuming that
we know how to compute square roots, we can compute the value of x for any
value of a.
When people talk about solving an equation, what they usually mean is something like “finding an equivalent relationship that is explicit in one of the variables.” In the context of this book, that’s what I will call an analytic solution, to
distinguish it from a numerical solution, which is what we are going to do next.
To demonstrate a numerical solution, consider the equation x 2 − 2x = 3.
You could solve this analytically, either by factoring it or by using the quadratic
equation, and you would discover that there are two solutions, x = 3 and x = −1.
p
Alternatively, you could solve it numerically by rewriting it as x = 2x + 3.
This equation is not explicit, since x appears on both sides, so it is not clear
that this move did any good at all. But suppose that we had some reason to
expect there to be a solution near 4. We could start with x = 4 as an “initial
p
guess,” and then use the equation x = 2x + 3 iteratively to compute successive
approximations of the solution.
Here’s what would happen:
>> x = 4;
>> x = sqrt(2*x+3)
x = 3.3166
>> x = sqrt(2*x+3)
x = 3.1037
205
206
Chapter 7 Zero-finding
>> x = sqrt(2*x+3)
x = 3.0344
>> x = sqrt(2*x+3)
x = 3.0114
>> x = sqrt(2*x+3)
x = 3.0038
After each iteration, x is closer to the correct answer, and after 5 iterations,
the relative error is about 0.1%, which is good enough for most purposes. You
could readily use a loop to automate the process.
Techniques that generate numerical solutions are called numerical methods.
The nice thing about the method I just demonstrated is that it is simple, but it
doesn’t always work as well as it did in this example, and it is not used very often
in practice. We’ll see one of the more practical alternatives in a minute.
7.5 Zero-finding
A nonlinear equation like x 2 − 2x = 3 is a statement of equality that is true for
some values of x and false for others. A value that makes it true is a solution; any
other value is a non-solution. But for any given non-solution, there is no sense of
whether it is close or far from a solution, or where we might look to find one.
To address this limitation, it is useful to rewrite nonlinear equations as zerofinding (or root-finding) problems:
• The first step is to define an “error function” that computes how far a given
value of x is from being a solution.
In this example, the error function is
f : x → x 2 − 2x − 3
Any value of x that makes f (x) = 0 is also a solution of the original equation.
More formally, solving the equation g (x) = h (x) is equivalent to finding the
zeros (or roots) of the function f (x) = g (x) − h (x).
• The next step is to find values of x that make f (x) = 0. These values are
called zeros of the function, or equivalently roots.
Zero-finding lends itself to numerical solution because we can use the values of
f , evaluated at various values of x, to make reasonable inferences about where to
look for zeros.
For example, if we can find two values x l and x r such that f (x l ) > 0 and
f (x r ) < 0, and f is continuous, then per the intermediate value theorem we can
7.6 fzero
207
be certain that there is at least one zero between x l and x r . In this case we would
say that x l and x r bracket a zero. This is the basis of bracketing methods of root
finding. Graphically this scenario might look like fig. 7.1.
Figure 7.1 Graphic example of x l and x r that bracket a zero.
If this was all you knew about f , where would you go looking for a zero? If
you said “halfway between x l and x r ,” then congratulations! You just invented a
numerical method called bisection!
If you said, “I would connect the dots with a straight line and compute the
zero of the line,” then congratulations! You just invented the secant (or linear
interpolation) method!
And if you said, “I would evaluate f at a third point, find the parabola that
passes through all three points, and compute the zeros of the parabola,” then...
well, you probably didn’t say that.
Finally, if you said, “I would use a built-in MATLAB function that combines
the best features of several efficient and robust numerical methods,” then you are
ready to go on to the next section.
7.6 fzero
fzero is a built-in MATLAB function that combines the best features of several efficient and robust numerical methods. Specifically, fzero is a bracketing method
of root finding that uses bisection, secant, and inverse quadratic interpolation
methods.
In order to use fzero, you have to define a MATLAB function that computes
the error function you derived from the original nonlinear equation, and you have
208
Chapter 7 Zero-finding
to provide two values that bracket the location of a zero or an initial guess of the
location of a zero; if you provide an initial guess, it is actually used to find brackets.
Providing brackets is preferred, but often an initial guess is more convenient.
We’ve already seen an example of an error function in Listing 7.1:
1
2
3
function res = error_func(x)
res = x.^2 - 2.*x - 3;
end
You can call error_func from the Command Window , and confirm that there are
zeros at 3 and -1.
>> error_func(3)
ans = 0
>> error_func(-1)
ans = 0
But let’s pretend that we don’t know exactly where the roots are; we only know
that one of them is near 4. Then we could call fzero like this:
>> fzero(@error_func, 4)
ans = 3
Success! We found one of the zeros.
The first argument is a function handle that names the M-file that evaluates
the error function. The @ symbol allows us to name the function without calling
it. The interesting thing here is that you are not actually calling error_func
directly; you are just telling fzero where it is. In turn, fzero calls your error
function—more than once, in fact.
The second argument is the initial guess. If we provide a different initial guess,
we get a different root (at least sometimes).
>> fzero(@error_func, -2)
ans = -1
Alternatively, if you know two values that bracket the root, you can provide
both:
>> fzero(@error_func, [2,4])
ans = 3
The second argument here is actually a vector that contains two elements. The
7.7 What could go wrong?
bracket operator is a convenient way (one of several) to create a new vector.
You might be curious to know how many times fzero calls your function, and
where. If you modify error_func so that it displays the value of x every time it is
called and then run fzero again, you get:
>> fzero(@error_func, [2,4])
x = 2
x = 4
x = 2.75000000000000
x = 3.03708133971292
x = 2.99755211623500
x = 2.99997750209270
x = 3.00000000025200
x = 3.00000000000000
x = 3
x = 3
ans = 3
Not surprisingly, it starts by computing f (2) and f (4) to confirm that they in
fact bracket a zero. After each iteration, the interval that brackets the root gets
smaller; fzero stops when the interval is so small that the estimated zero is correct
to 16 digits, 2.2204 × 10−16 to be exact. If you don’t need that much precision, you
can tell fzero to give you a quicker, dirtier answer (see the documentation for
details about changing the tolerance).
7.7 What could go wrong?
The most common problem people have with fzero is leaving out the @. In that
case, you get something like:
>> fzero(error_func, [2,4])
Error using error_func (line 2)
Not enough input arguments.
Which is a very confusing error message. The problem is that MATLAB treats
the first argument as a function call, so it calls error_func with no arguments.
Since error_func requires one argument, the message indicates that the input
argument is “undefined,” although it might be clearer to say that you haven’t
provided a value for it.
Another common problem is writing an error function that never assigns a
value to the output variable. In general, functions should always assign a value to
the output variable, but MATLAB doesn’t enforce this rule, so it is easy to forget.
For example, if you were to re-write Listing 7.1:
209
210
Chapter 7 Zero-finding
1
2
3
function res = error_func(x)
y = x.^2 - 2.*x -3
end
and then call it from the Command Window :
>> error_func(4)
y = 5
It looks like it worked, but don’t be fooled. This function assigns a value to y,
and it displays the result, but when the function ends, y disappears along with
the function’s workspace. If you try to use it with fzero, you get
>> fzero(@error_func, [2,4])
y = -3
Error using fzero (line 233)
FZERO cannot continue because user-supplied function_handle
==> error_func failed with the error below.
Output argument "res" (and maybe others) not assigned
during call to "error_func".
If you read it carefully, this is a pretty good error message (with the quibble
that “output argument” is not a good synonym for “output variable”).
You would have seen the same error message when you called error_func
from the Command Window , if only you had assigned the result to a variable:
>> x = error_func(4)
y = 5
Error in error_func (line 2)
y = x.^2 - 2.*x -3;
Output argument "res" (and maybe others) not assigned during
call to "error_func".
You can avoid all of this if you remember these two rules:
• Functions should always assign values to their output variables.
• When you call a function, you should always do something with the result
(either assign it to a variable or use it as part of an expression, etc.).
7.8 Finding an initial guess
When you write your own functions and use them yourself, it is easy for
mistakes to go undetected. But when you use your functions with MATLAB
functions like fzero, you have to get it right!
Yet another thing that can go wrong: if you provide an interval for the initial
guess and it doesn’t actually contain a root, you get
>> fzero(@error_func, [0,1])
Error using fzero (line 274)
The function values at the interval endpoints must differ in
sign.
Or it may be that the interval contains a root, it is just not guaranteed per the
intermediate value theorem. This is where understanding the basis of fzero is
beneficial.
There is one other thing that can go wrong when you use fzero, but this
one is less likely to be your fault. It is possible that fzero won’t be able to find a
root. fzero is generally pretty robust, so you may never have a problem, but you
should remember that there is no guarantee that fzero will work, especially if
you provide a single value as an initial guess. Even if you provide an interval that
brackets a root, things can still go wrong if the error function is discontinuous.
Remember the basis of bracketing methods. If your function is continuous
and changes signs over an interval, then at least one zero exists.
211
t Well, okay, there are exceptions to the rule that
functions should always
assign values to their
output variables, including find_triples (Listing 5.15). Functions that
don’t return a value are
sometimes called “commands,” because they do
something (like display values or generate a figure) but
either don’t have an output
variable or don’t make an
assignment to it.
7.8 Finding an initial guess
The better your initial guess (or interval) is, the more likely it is that fzero will
work, and the fewer iterations it will need.
When you are solving problems in the real world, you will usually have some
intuition about the answer. This intuition is often enough to provide a good initial
guess for zero-finding.
Another approach is to plot the function and see if you can approximate the
zeros visually. If you have a function, like error_func that takes a single input
variable and returns a single output variable, you can plot it with fplot:
>> fplot(@error_func, [-2,5])
t Note that in previous
semesters we would use
ezplot. However, the MATLAB documentation now
indicates that ezplot is
not recommended, to use
fplot instead.
212
Chapter 7 Zero-finding
12
10
8
6
4
2
0
-2
-4
-2
-1
0
1
2
3
4
5
Figure 7.2 fplot(@error_func, [-2,5])
The first argument is a function handle; the second is the interval you want to
plot the function in. You can modify the plot appearance just as you did before
with plot.
>> fplot(@error_func, [-2,5],'-ro')
12
10
8
6
4
2
0
-2
-4
-2
-1
0
1
2
3
4
Figure 7.3 fplot(@error_func, [-2,5],’-ro’)
5
7.8 Finding an initial guess
213
As this example shows you, fplot calls your function several times, so you
will probably want to make sure your function is silent before you plot.
All of the other tricks we used before with plot work here too. For example,
you can add a title and axis labels, and you can save and print your figure. In
general, when using fplot to obtain estimates of location of zeros, we will not
worry too much about making the plot pretty. But fplot is a very convenient
function, and you may find it useful for other applications.
Last, students often find it helpful to plot a reference y = 0 line to help identify
zeros. This can readily be accomplished. Here’s an example where the reference
line is plotted as dotted black line:
>> hold on
>> fplot(@error_func, [-2,5])
>> plot([-2,5],[0,0])
12
10
8
6
4
2
0
-2
-4
-2
-1
0
1
2
3
4
5
Figure 7.4 fplot(@error_func, [-2,5]) and plot([-2,5],[0,0])
And if that seems like a bit much, you could alternatively just tell MATLAB to
turn the plot grid on. That would look something like this:
>> fplot(@error_func, [-2,5])
>> grid on
214
Chapter 7 Zero-finding
12
10
8
6
4
2
0
-2
-4
-2
-1
0
1
2
3
4
5
Figure 7.5 fplot(@error_func, [-2,5]) and grid on
7.9 More name collisions
Functions and variables occupy the same “name-space,” which means that whenever a name appears in an expression, MATLAB starts by looking for a variable
with that name, and if there isn’t one, it looks for a function.
As a result, if you have a variable with the same name as a function, the variable shadows the function. For example, if you assign a value to sin, and then try
to use the sin function, you might get an error:
>> sin = 3;
>> x = 5;
>> sin(x)
Index exceeds matrix dimensions.
In this example, the problem is clear. Since the value of sin is a scalar, and a
scalar is really a 1×1 matrix, MATLAB tries to access the 5th element of the matrix
and finds that there isn’t one. Of course, if there were more distance between the
assignment and the “function call,” this message would be pretty confusing.
But the only thing worse than getting an error message is not getting an error
message. If the value of sin was a vector, or if the value of x was smaller, you
would really be in trouble.
7.9 More name collisions
>> sin = 3;
>> sin(1)
ans = 3
Just to review, the sine of 1 is not 3!
The converse error can also happen if you try to access an undefined variable
that also happens to be the name of a function. For example, if you have a function named f, and then try to increment a variable named f (and if you forget to
initialize f), you get
>> f = f+1
Error: "f" previously appeared to be used as a function or
command, conflicting with its use here as the name of a variable.
A possible cause of this error is that you forgot to initialize the
variable, or you have initialized it implicitly using load or eval.
At least, that’s what you get if you are lucky. If this happens inside a function,
MATLAB tries to call f as a function, and you get this
Error using f (line 2)
Not enough input arguments.
There is no universal way to avoid these kind of collisions, but you can improve
your chances by choosing variable names that don’t shadow existing functions,
and by choosing function names that you are unlikely to use as variables. That’s
why in Section 7.3 I called the error function error_func rather than f. I often
give functions names that end in func, so that helps too.
This warning came up previously when discussing variable names. The MATLAB documentation page “Variable Names” recommends the use of the exist
function if you are unsure. exist returns 0 if there are no existing variables,
functions, or other artifacts with the proposed name. For example:
>> exist test_func
ans = 0
or equivalently
>> exist('test_func')
ans = 0
215
216
Chapter 7 Zero-finding
7.10 Debugging in four acts
When you are debugging a program, and especially if you are working on a hard
bug, there are four things to try:
reading: Examine your code, read it back to yourself, and check that it means
what you meant to say.
running: Experiment by making changes and running different versions. Often
if you display the right thing at the right place in the program, the problem
becomes obvious, but sometimes you have to spend some time to build
scaffolding.
ruminating: Take some time to think! What kind of error is it: syntax, run-time,
logical? What information can you get from the error messages, or from the
output of the program? What kind of error could cause the problem you’re
seeing? What did you change last, before the problem appeared?
retreating: At some point, the best thing to do is back off, undoing recent changes,
until you get back to a program that works, and that you understand. Then
you can starting rebuilding.
Beginning programmers sometimes get stuck on one of these activities and
forget the others. Each activity comes with its own failure mode.
For example, reading your code might help if the problem is a typographical
error, but not if the problem is a conceptual misunderstanding. If you don’t
understand what your program does, you can read it 100 times and never see the
error, because the error is in your head.
Running experiments can help, especially if you run small, simple tests. But
if you run experiments without thinking or reading your code, you might fall
into a pattern I call “random walk programming,” which is the process of making
random changes until the program does the right thing. Needless to say, random
walk programming can take a long time.
The way out is to take more time to think. Debugging is like an experimental
science. You should have at least one hypothesis about what the problem is. If
there are two or more possibilities, try to think of a test that would eliminate one
of them.
Taking a break sometimes helps with the thinking. So does talking. If you
explain the problem to someone else (or even yourself), you will sometimes find
the answer before you finish asking the question.
But even the best debugging techniques will fail if there are too many errors,
or if the code you are trying to fix is too big and complicated. Sometimes the
best option is to retreat, simplifying the program until you get to something that
works, and then rebuild.
Beginning programmers are often reluctant to retreat, because they can’t
stand to delete a line of code (even if it’s wrong). If it makes you feel better, copy
7.11 Examples
217
your program into another file before you start stripping it down. Then you can
paste the pieces back in a little bit at a time.
To summarize, here’s the Ninth Theorem of debugging:
Finding a hard bug requires reading, running, ruminating, and
sometimes retreating. If you get stuck on one of these activities, try
the others.
7.11 Examples
Example 7.1
(a) Write a function called cheby6 that evaluates the 6th Chebyshev polynomial.
It should take an input variable, x, and return
32x 6 − 48x 4 + 18x 2 − 1
(7.1)
(b) Use fplot to display a graph of this function in the interval from 0 to 1.
Estimate the location of any zeros in this range.
(c) Use fzero to find as many different roots as you can. Does fzero always
find the root that is closest to the initial guess?
Solution: (Link to screen cast and accompanying M-file.) Let’s begin by creating
the M-file cheby6.
Listing 7.2 cheby6.m
1
2
3
4
5
% res = cheby6(x)
% Function to evaluate the 6th Chebyshev polynomial
function res = cheby6( x )
res = 32.*x.^(6) -48.*x.^(4) + 18.*x.^(2) -1;
end
To get an estimate of the locations of the roots over the interval 0 to 1, let’s use
fplot.
>>
>>
>>
>>
hold on
fplot(@cheby6,[0,1])
title('cheby6')
plot([0,1],[0,0],':k')
218
Chapter 7 Zero-finding
cheby6
1
0.8
0.6
0.4
0.2
0
-0.2
-0.4
-0.6
-0.8
-1
0
0.2
0.4
0.6
0.8
1
Figure 7.6 fplot(@cheby6,[0,1])
We appear to have roots near 0.2, 0.7, and 0.9. Let’s use fzero with these initial
guesses to find them.
>> zero_1 = fzero(@cheby6,0.2)
zero_1 = 0.2588
>> zero_2 = fzero(@cheby6,0.7)
zero_2 = 0.7071
>> zero_3 = fzero(@cheby6,0.9)
zero_3 = 0.9659
Lastly, we are asked if fzero always returns the root closest to the initial guess.
The key is to be careful. Here’s an example:
>> fzero(@cheby6,0.85)
ans = 0.9659
>> fzero(@cheby6,0.84)
ans = 0.7071
>> fzero(@cheby6,0.49)
ans = 0.7071
>> fzero(@cheby6,0.48)
ans = 0.2588
A small change in the guess causes you to find a different root. Remember that
7.11 Examples
219
MATLAB uses the guess to find brackets of the root. This is one of the main reason
I said that specifying a bracket is preferred, while specifying an initial condition
is typically more convenient. When we have multiple roots, if I specify a bracket
around the root of interest, I will be certain to obtain that root. When I specify an
initial guess, it is not clear to me how MATLAB searches for a bracket.
Example 7.2
The density of a duck, ρ d , is 0.3 g/cm3 (0.3 times the density of water, ρ w = 1
g/cm3 ). The volume of a sphere with radius r is 43 πr 3 . If a sphere with radius r is
submerged in water to a depth d , the volume (V ) of the sphere below the water
line is
V=
π
(3r d 2 − d 3 ) as long as d < 2r
3
An object floats at the level where the weight of the displaced water equals the
total weight of the object. Assuming that a duck is a sphere with radius 10 cm, at
what depth does a duck float?
Here are some suggestions about how to proceed:
• Write an equation relating ρ, d and r . That is, perform a force balance.
• Rearrange the equation so the right-hand side is zero. Our goal is to find
values of d that are roots of this equation.
• Write a MATLAB function that evaluates this function. Test it, then make it a
silent function.
• Use fplot to make a guess about the brackets or to make a guess about the
value of d 0 to use as a starting place. With physical systems you can often
come up with a bracket or initial guess without needing fplot. This is a
perfect example.
• Use fzero to find the root.
• Check to make sure the result makes sense. In particular, check that d < 2r ,
because otherwise the volume equation doesn’t work! The sphere would be
submerged in water.
Solution:
Let’s begin by writing an expression for the weight of the duck (Wd ) and the weight
of the displaced water (Ww )
4
4
Wd = ρ d g V = ρ d g πr 3 = 0.3ρ w g πr 3
3
3
Ww = ρ w g V = ρ w g
π
(3r d 2 − d 3 )
3
Our criteria for buoyancy is Wd = Ww , giving
(7.2)
(7.3)
220
Chapter 7 Zero-finding
4
π
0.3ρ w g πr 3 = ρ w g (3r d 2 − d 3 )
3
3
(7.4)
(0.3)(4)r 3 = (3r d 2 − d 3 )
(7.5)
which simplifies to
Putting this expression in a form suitable for fzero
(0.3)(4)r 3 − (3r d 2 − d 3 ) = 0
(7.6)
We know that r = 10 cm, so that the only unknown is d , which you can think of as
our x in this case. (The units of d will agree with r .)
Next, let’s create a function called duck that we will pass to fzero.
Listing 7.3 duck.m
1
2
3
4
5
6
% res = duck(x)
% Function to find the required depth of a duck to remain
% buoyant in water
function res = duck( x )
res = 0.3.*4.*10.^(3)-(3.*10.*x.^(2)-x.^(3));
end
To get an estimate of the locations of the roots over the interval 0 to 2r = 20 cm,
let’s use fplot.
>>
>>
>>
>>
hold on
fplot(@duck,[0,20])
title('duck')
plot([0,20],[0,0],':k')
7.12 Functions: what we’ve done so far
221
duck
1500
1000
500
0
-500
-1000
-1500
-2000
-2500
-3000
0
5
10
15
20
Figure 7.7 fplot(@duck,[0,20])
It looks like we only have one solution, which is near d = 8 cm. Let’s use this as our
initial guess and have fzero find the root.
>> d = fzero(@duck,8)
d = 7.2651
Nice! So for our spherical duck with a radius of 10 cm (or diameter of 20 cm),
7.2651 cm is below the surface of the pond. This satisfies the constraint that d < 2r .
For a buoyancy problem, we expect there only to be one unique solution. So for
problems of this type, you might instead specify the range of all possible values.
>> d = fzero(@duck,[0,20])
d = 7.2651
As a final note, in my presented solution I simplified the force balance expression
in an attempt to make the resulting error function as simple as possible. However,
know that this is not necessary. In fact, I often do not simplify expressions so as
not to introduce the possibility of errors.
Before moving on, if interested, have a look at the MATLAB documentation
page “Roots of Scalar Functions.”
7.12 Functions: what we’ve done so far
So far we have created function M-files using the Editor. In Chapter 5 when we
first introduced the use of functions, their use was motivated by the fact that
222
Chapter 7 Zero-finding
each function has its own workspace, and that inputs and outputs are defined
carefully to avoid unexpected interactions. Additionally, for large calculations we
have seen that they allow us to modulate our code, making our code both more
readable and easier to debug.
We have seen so far that fzero and fplot both expect a function with just a
single input variable. But what if we wish to pass an extra parameter? This can be
accomplished using an anonymous function, which we will discuss here.
Additionally, the very first section of this text, Section 1.1, is titled “A glorified
calculator”. There are many times where you will want to obtain an answer to a
problem quickly. Using MATLAB as a calculator in these cases is desirable. In this
chapter we will additionally show how fzero and fplot can be used in calculator
mode with the help of anonymous functions. I do, however, recommend writing
script and function M-files whenever possible. This will allow you to document
and save your work.
t There are two additional
techniques that can be used
7.13 Anonymous function
to pass extra parameters.
One is the use of nested
functions. We will use
An anonymous function is a function that is not stored in a file, but is instead
nested functions later in
stored as a variable (a function_handle). An anonymous function behaves just
the course, and I would
like a function stored in a file, except it can only contain a single executable
suggest you avoid them for
statement.
now as we are just getting
Imagine that you wish to evaluate the cube root of a number. You know this
started. With nested funcis
a
calculation you will need to perform often, so you decide to write a function
tions the workspace rules
M-file.
This leads to
change slightly, which is
why I suggest to wait. The
other is to use global variListing 7.4 cubert.m
ables. However, as MATLAB
notes, global variables carry 1 function res = cubert(X)
noticeable risks, and should 2
res = X.^(1/3);
be used sparingly, if at all.
3 end
As a note, GNU Octave does
not support nested functions, but does support
Then from the Command Window
anonymous functions and
global variables.
>> cubert(9)
ans = 2.0801
>> cubert(27)
ans = 3
>> Y = [9, 24, 56, 87];
>> cubert(Y)
ans = 2.0801
2.8845
3.8259
4.4310
7.13 Anonymous function
223
Beautiful! Now imagine that this is a calculation you will need to perform often,
but just within this session, and maybe you are not using your own computer
so you do not wish to (or can not) save a file to the computer. Or you are in the
middle of an exam and do not have the time to write a function file! We can
instead create an anonymous function in the Command Window . Repeating the
same calculations:
>> crt = @(X) X.^(1/3);
>> crt(9)
ans =
2.0801
>> crt(27)
ans =
3
>> Y = [9, 24, 56, 87];
>> crt(Y)
ans =
2.0801
2.8845
3.8259
4.4310
Notice that in the assignment statement for crt, the first term on the right is a handle, @; the variable therefore corresponds to a function handle. The parentheses
immediately following the handle indicate the input variable(s).
Just as with function files, anonymous functions can have more than a single
input variable. To compute the length of the hypotenuse of a triangle:
>> hyp = @(A,B) sqrt(A.^(2)+B.^(2));
>> hyp(3,4)
ans =
5
The answer can also be stored to a new variable rather than the default ans which
will be overwritten the next calculation:
>> hyp = @(A,B) sqrt(A.^(2)+B.^(2));
>> sol = hyp(3,4)
sol =
5
Another very useful use of anonymous functions is to quickly plot a function
over a range using fplot. Imagine you wish to plot the function y = a sin(bx) + c,
t Remember a script file is
nothing more than a document containing a series
of commands you would
otherwise execute in the
Command Window . When
we run a script, we execute
commands line-by-line as
if we had entered them in
the Command Window . One
could therefore create a
script that contains anonymous functions to save your
work. Anonymous functions could also be used in
function files.
224
Chapter 7 Zero-finding
where a = 4, b = 2 and c = 1, over the range 0 to 2π. Let’s start using what we have
done so far and create a function M-file.
Listing 7.5 sin_fun.m
1
2
3
4
5
6
function res = sin_fun(X)
a = 4;
b = 2;
c = 1;
res = a.*sin(b.*X)+c;
end
Then
>> fplot(@sin_fun,[0,2*pi])
We could equivalently do this from the Command Window with an anonymous
function. I will declare parameters a, b, and c so that we can latter show issues
that might arise with parameters.
>> a = 4; b = 2; c = 1;
>> sfun = @(X) a.*sin(b.*X)+c;
>> fplot(sfun,[0,2*pi])
5
4
3
2
1
0
-1
-2
-3
0
1
2
3
4
Figure 7.8 fplot(sfun,[0,2*pi])
5
6
7.14 What could go wrong?
225
Notice that when using fplot to plot our anonymous function, we do not need
@ in the function call. This is because the anonymous function, here sfun, is
already of type function handle.
Another use of anonymous functions is to quickly find the zeroes of a function.
For example, find where y = 4 sin(2x) + 1 is equal to zero over the range 0 to 2. I
will confirm the solution by also using our function file.
>> sol = fzero(sfun,[0,2])
sol =
1.6971
>> sol = fzero(@sin_fun,[0,2])
sol =
1.6971
7.14 What could go wrong?
The main issue to look out for is when using anonymous functions with parameters. For instance, we were just looking at the function y = a sin(bx) + c, where
a = 4, b = 2 and c = 1. Let’s start by evaluating the function at x = 2. Throughout
this section will will evaluate the function using both our function file and anonymous function for comparison.
>> sin_fun(2)
ans =
-2.0272
>> sfun(2)
ans =
-2.0272
A perfect match! Now what if instead we wanted to change the parameter c from
1 to 2. For the case of our function file, I will create a copy and save as sin_fun_2,
where I change the value of c.
Listing 7.6 sin_fun_2.m
1
2
3
4
5
6
function res = sin_fun_2(X)
a = 4;
b = 2;
c = 2;
res = a.*sin(b.*X)+c;
end
226
Chapter 7 Zero-finding
And evaluating
>> sin_fun_2(2)
ans =
-1.0272
The resulting value increases by 1 as expected. How do we update our anonymous function? It first might be tempting to simply update the value of c in the
Command Window and then re-evaluate the function:
>> c = 2;
>> sfun(2)
ans =
-2.0272
But this is wrong, very wrong. Remember, whenever we assign a number to a
variable, whenever MATLAB comes across the variable name, it substitutes in
its value. Therefore, when we originally defined our anonymous function, it was
defined using the original values of a, b, and c. After its creation it has no recollection of a, b, and c; it just used their values. Therefore, when we update the value
of c, we need to re-define the function. Let’s try again:
>> c = 2;
>> sfun = = @(X) a.*sin(b.*X)+c;
>> sfun(2)
ans =
-1.0272
Now that is better.
7.15 Saving anonymous functions
t What is meant by another
or new session? Well a new
session would be if you
closed MATLAB and then
re-opened it later.
As already mentioned, it is always a good idea to write script to document your
work. Remember a script M-file is a file that contains human-readable MATLAB
code.
You could alternatively save your anonymous function (or your entire workspace)
as a MAT-file for use in another session. A MAT-file is not human-readable. It is a
file that contains MATLAB formatted data. Data can be written to or loaded from a
MAT-file using the save and load command respectfully. To save our anonymous
function sfun to a file sfun.mat, the command would be save sfun.mat sfun.
The file name does not need to be the same as the anonymous function, but I
do so here because it is the function of interest. Then to load the anonymous
function in a new session, with the MAT-file in your path, the command would
be load(’sfun.mat’). I will demonstrate the commands in action below by
7.16 Revisiting the duck
227
simulating a new session by clearing all variables after saving the anonymous
function. At each step I will evaluate the function at x = 2.
>> sfun(2)
ans =
-1.0272
>> save sfun.mat sfun
>> clear variables
>> sfun(2)
Undefined function or variable 'sfun'.
>> load('sfun.mat')
>> sfun(2)
ans =
-1.0272
This is NOT generally something I find myself using, but I thought I would
mention it for completeness. I typically prefer to write scripts and save my work
in human-readable form.
7.16 Revisiting the duck
As a final example, let’s revisit Example 7.2, our duck buoyancy problem. In the
problem we were solving for the depth (d ) to which a duck of radius r was submerged in water of density ρ using fzero. fzero expects a function to which we
pass only a single variable. We therefore specified r and ρ in the function to solve.
Here, let’s generalize the function to allow us to pass r and ρ too, and see how an
anonymous function can be used to pass these extra parameters. This would be
useful, for example, if you wished to look at the effect of these parameters. The
alternative would be to edit and re-save the original error function M-file each
time you wished to make a change.
Let’s start by working out a new expression that contains ρ (for an arbitrary
liquid) and r as parameters. Rather than writing ρ d as 0.3ρ w , let’s just use 0.3
since the density of the liquid will change. We will just need to be sure we use
correct units throughout. Density in g/cm3 and length in cm. Beginning with
eq. (7.4):
4
π
0.3g πr 3 = ρg (3r d 2 − d 3 )
3
3
(7.7)
(0.3)(4)r 3 = ρ(3r d 2 − d 3 )
(7.8)
Simplifying
Putting in a form for fzero
228
Chapter 7 Zero-finding
(0.3)(4)r 3 − ρ(3r d 2 − d 3 ) = 0
(7.9)
Now lets create a new function duck2 to which we will also pass parameters r
and ρ.
Listing 7.7 duck2.m
1
2
3
4
5
6
7
8
% res = duck2(x,r,rho)
% Function to find the required depth of a duck to remain
% buoyant in water. duck2 requires that we pass parameters
% r (radius of duck) and rho (density of the liquid), in
% addition to the depth (x) which is what we will solve for.
function res = duck2( x,r,rho )
res = 0.3.*4.*r.^(3)-rho.*(3.*r.*x.^(2)-x.^(3));
end
Next, let’s verify that we get the same solution as before with duck
>> d0 = 7;
>> dref = fzero(@duck,d0)
dref = 7.2651
>> r = 10;
>> rho = 1;
>> duck_zero = @(X) duck2(X,r,rho);
>> d = fzero(duck_zero,d0)
d = 7.2651
A perfect match! If you wanted to look at different values of r and ρ, you would
update their values, re-define the anonymous function, and then re-solve using
fzero. Cool!
After seeing this example, think of how you might be able to use anonymous
functions to generalize the Antoine equation functions you have already written?
We could write a function to perform an Antoine equation calculation for the
general case in which we pass the temperature in addition to parameters A, B , and
C . This is great in that the function can be used for any fluid. But it is annoying
in that if for a given problem I am only interested in a single fluid, say ethanol,
I need to pass the same values of A, B , and C every call. I could instead use an
anonymous function to pass A, B , and C , to create an ethanol-specific function
for the problem at hand. This is a good practice because if I know my general
code is bug-free, I can rest assured my anonymous functions is bug-free too, and
it can streamline the calculations.
7.17 Numerical methods: zero-finding
229
7.17 Numerical methods: zero-finding
7.17.1 The bisection method
The function fzero is great, and in general, I would recommend using MATLAB’s
built-in (intrinsic) functions whenever possible. You can be confident they are bug
free and efficient. Nonetheless, it is always valuable to know how the functions
(here fzero) work. This will help you understand errors and what may go wrong,
and how to tweak the options (when available) for a particular problem. Or at the
very least, it will help you appreciate the hard work that went in to developing
MATLAB.
With that, let’s discuss the bisection method. It is a numerical method that
falls in the category of bracketing methods for zero-finding. To keep matters
simple for now, think of the the bisection method in the context of Example 7.2,
our duck buoyancy problem. This will serve as a good starting point since there is
only one zero (or root or solution to our problem).
In general, our goal is to find the value of x = x ans for which f (x ans ) = 0. To
one side of x ans , the function will be positive, on the other side it will be negative,
and somewhere in between it is zero; the basis of bracketing methods is the
intermediate value theorem, which you should have encountered in your first
Calculus course. If x l is the value of x to the left and x r is the value of x to the right
of the solution, then we have f (x r ) f (x l ) < 0. This is the premise of the bisection
method.
1. Choose a value of x l and x r (the range of the search) such that the function changes sign over the interval. Put differently, x l and x r should have
different signs such that f (x r ) f (x l ) < 0.
2. Estimate the root as the mid-point between x l and x r :
x ans =
1
(x l + x r )
2
3. Evaluate f (x ans ).
4. Next, we need to decide what to do:
(a) If f (x r ) f (x ans ) < 0, then the root lies in this new sub-interval. Set
x l = x ans and return to Step 2.
(b) Else if f (x l ) f (x ans ) < 0, then the root must lie in this new sub-interval.
Set x r = x ans and return to Step 2.(Note, you could just as well check if
f (x r ) f (x ans ) > 0.)
(c) Else if f (x ans ) = 0, we have found our answer, terminate the calculation. (Note, since anything times anything is 0, you could just as well
check it f (x r ) f (x ans ) = 0.)
t Remember, in Example 3.6
we wrote a script to compute sin(x) using a Taylor
series expansion. This is
what MATLAB does, and
gave us an appreciation of
the numerical error in the
calculation.
230
Chapter 7 Zero-finding
t Another common fzero
error is the failure to provide correct brackets to the
zero. Likewise, if you provide a guess of location of
the zero that is very bad,
MATLAB will be unable to
find brackets.
Eventually, the interval becomes so small that we converge on our answer. So
long as x l and x r bracket a zero, and the function is continuous over the range, it
will find a solution. Before we go ahead and write a MATLAB function to perform
the bisection method, let’s think about the settings or options that need to be
specified to get this to work.
First, we need to decide how close we need be to zero to stop. We typically
call this the tolerance. Remember, in MATLAB sin(pi)=1.2246e-16, and not
exactly 0. Also note that since we are looking for a value close to zero, you will
likely encounter values that are positive and negative. Therefore, do not simply
check that the value is < tolerance; all negative values will satisfy this. Rather, use
absolute values or the square of the value. (A negative times a negative is positive.)
As the tolerance decreases, the number of iterations required for convergence
increases, which brings us to our second setting.
Second, we need to specify how many iterations to perform before giving up.
Perhaps the specified tolerance was much too small such that your code does not
converge in a reasonable amount of time. This is also useful in the early stages of
code developing where a bug might cause your program to otherwise run forever.
Third, we saw that with fzero we can specify either an initial guess or a range.
How do you use the bisection method if only an initial guess is provided? Well, we
assume the guess is close to the root, so we can use this to guess an initial value of
x l and x r . If you wanted to provide this functionality, you would need to decide
how to estimate this range.
Enough talk, let’s do it!
7.17 Numerical methods: zero-finding
Listing 7.8 bisection_zero.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
% res = bisection_zero(fcn_handle,Bracket,maxit,tol)
% Function to perform bisection method to find the root
% of a function (evaluated/provided in fcn_handle)
% in the range xl to xr. maxit sets the maximum number
% of iterations and tol sets the tolerance for convergence.
% Bracket is a vector where the first element is xl and the
% second element is xr.
function res = bisection_zero(fcn_handle,Bracket,maxit,tol)
%hold on
% Start by un-packing Bracket into x1 and xr
xl = Bracket(1);
xr = Bracket(2);
% Check to make sure f(xl) and f(xr) have opposite signs.
% If not, don't do anything.
if fcn_handle(xl)*fcn_handle(xr) > 0
%f(xl) and f(xr) have the same sign
disp('f(xl) and f(xr) have the same sign. Exit.')
% else, f(xl) and f(xr) have the opposite sign,proceed
else
for i=1:maxit
% Estimate location of zero as midpoint between
% xl and xr
xans = 0.5*(xl+xr);
fxans = fcn_handle(xans);
fxl = fcn_handle(xl);
fxr = fcn_handle(xr);
% Check if we found our root
if fxans^(2) < tol^(2)
res = xans;
%i
break
% Otherwise, update our brackets
elseif fxl*fxans < 0
xr = xans;
elseif fxr*fxans < 0
xl = xans;
else
% We should never get here, but just in case
disp('Critical error. Exit')
disp('Current estimate:')
xans
fxans
break
231
232
Chapter 7 Zero-finding
44
45
46
47
48
49
50
51
52
53
54
55
56
end
end
end
end
% Check to see if we reached the maximum number of
% iterations. If we did, print the current estimates.
if i == maxit
disp('Reached maximum number of iterations. Exit')
disp('Current estimate:')
xans
fxans
end
%plot(i,xans,'ro')
Notice the variable fcn_handle and its use in the function. Just as we use a
function handle to tell fzero which function to find the roots of, we can use a
function handle here too.
Now let’s see it in action.
>> xl = 0;
>> xr = 20;
>> tolerance = 1e-12;
>> maxiter = 1000;
>> bisection_zero(@duck,[xl,xr],maxiter,tolerance)
ans = 7.2651
This is exactly what we had before. If you uncomment lines 9 and 53, you can
generate a plot of the estimate of the root location versus the iteration number,
which is shown below.
7.17 Numerical methods: zero-finding
233
10
9.5
9
8.5
8
7.5
7
6.5
6
5.5
5
0
10
20
30
40
50
Figure 7.9 The current estimate of the root location versus the iteration
number for our duck buoyancy problem using bisection_zero.
Let’s walk through what MATLAB is doing step-by-step. Let’s start by revisiting
fig. 7.1, which is for our duck problem, which is shown again in the margin as
fig. 7.10 for convenience. We begin with x l = 0 and x r = 20, which bracket our
zero, with the zero we are searching for located where the red lines cross. Using
bisection method, our guess of the zero location each iteration is the midpoint
between x l and x r . Our first guess then is x ans = 10.
Figure 7.10 Our initial estimate of
x l and x r that bracket our zero.
234
Chapter 7 Zero-finding
Figure 7.11 Our first estimate of x ans .
We find that x l and our current estimate of x ans bracket our solution. So we
replace x r with x ans and repeat. This interval 0 to 10 then gets cut in half, and our
second guess of the zero location is x ans = 5.
Figure 7.12 Our second estimate of x ans .
We find now that our current estimate of x ans and x r bracket the solution. So
we replace x l with x ans and repeat. (At this point it becomes difficult for me to
sketch clear figures... but I hope you get the idea.)
7.17 Numerical methods: zero-finding
The interval 5 to 10 then gets cut in half, and our third guess of the zero
location is 7.5. At this point the range over which we are searching is 2.5, 8 times
smaller than the original range of 20. The refinement continues, and we see that
by the 10th iteration we appear to be very close to our final answer.
Next, let’s see what happens if we provide a value of x r < 7.2651 and then a
small value for the maximum number of iterations.
>> bisection_zero(@duck,[xl,5],maxiter,tolerance)
f(xl) and f(xr) have the same sign. Exit.
>> bisection_zero(@duck,[xl,xr],4,tolerance)
Reached maximum number of iterations. Exit
Current estimate:
xans = 6.2500
fxans = 272.2656
The last point I would like to make is if I uncomment line 30, I can see how many
iterations it takes to find the root. We are using format short, so our answer
only contains four decimal places. If I set the tolerance to 1×10−4 , I get our answer
after 25 iterations. As the tolerance becomes smaller, the number of interactions
increases. For a tolerance of 1 × 10−12 , we require 51 iterations. While this is not
a problem here, it is something to keep in mind if you have a more complicated
problem.
Last, before moving on, it is useful to demonstrate that we can more properly
extract additional information by returning additional/optional variables. I will
save a copy of the function and demonstrate how we can return the number of
iterations and the value of the error function at the solution.
235
236
Chapter 7 Zero-finding
Listing 7.9 bisection_zero_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
% res = bisection_zero_2(fcn_handle,Bracket,maxit,tol)
% Function to perform bisection method to find the root
% of a function (evaluated/provided in fcn_handle)
% in the range xl to xr. maxit sets the maximum number
% of iterations and tol sets the tolerance for convergence.
% Bracket is a vector where the first element is xl and the
% second element is xr.
function [res,i,fxans] = bisection_zero_2(fcn_handle,Bracket,maxit,tol)
%hold on
% Start by un-packing Bracket into x1 and xr
xl = Bracket(1);
xr = Bracket(2);
% Check to make sure f(xl) and f(xr) have opposite signs.
% If not, don't do anything.
if fcn_handle(xl)*fcn_handle(xr) > 0
%f(xl) and f(xr) have the same sign
disp('f(xl) and f(xr) have the same sign. Exit.')
% else, f(xl) and f(xr) have the opposite sign,proceed
else
for i=1:maxit
% Estimate location of zero as midpoint between
% xl and xr
xans = 0.5*(xl+xr);
fxans = fcn_handle(xans);
fxl = fcn_handle(xl);
fxr = fcn_handle(xr);
% Check if we found our root
if fxans^(2) < tol^(2)
res = xans;
%i
break
% Otherwise, update our brackets
elseif fxl*fxans < 0
xr = xans;
elseif fxr*fxans < 0
xl = xans;
else
% We should never get here, but just in case
disp('Critical error. Exit')
disp('Current estimate:')
xans
fxans
break
7.17 Numerical methods: zero-finding
44
45
46
47
48
49
50
51
52
53
54
55
56
end
end
end
end
% Check to see if we reached the maximum number of
% iterations. If we did, print the current estimates.
if i == maxit
disp('Reached maximum number of iterations. Exit')
disp('Current estimate:')
xans
fxans
end
%plot(i,xans,'ro')
>> xl = 0;
>> xr = 20;
>> tolerance = 1e-12;
>> maxiter = 1000;
>> d = bisection_zero(@duck,[xl,xr],maxiter,tolerance)
d = 7.2651
>> d = bisection_zero_2(@duck,[xl,xr],maxiter,tolerance)
d = 7.2651
>> [d,niter,fans] = bisection_zero_2(@duck,[xl,xr],maxiter,tolerance)
d = 7.2651
niter = 51
fans = -4.5475e-13
7.17.2 The secant (or linear interpolation) method
More efficient techniques exist. One such technique that is very similar to the
bisection method is the secant (or linear interpolation) method. All that differs
is Step 2, guessing a value of x ans . With the bisection method, we guess a value
of x ans as the mid-point between x l and x r . Therefore, each iteration the search
interval is halved. To guess a value of x ans with the linear interpolation method,
we connect f (x l ) and f (x r ) with a straight line and find where the line equals
0 (or crosses the x-axis). We use this value as our guess of x ans . A schematic of
how we would find our first guess of x ans for our duck buoyancy problem using
linear interpolation is provided in fig. 7.13. We see that this clearly differs from
the midpoint used by the bisection method.
237
238
Chapter 7 Zero-finding
Figure 7.13 Finding our first guess of x ans for our duck buoyancy problem
using linear interpolation.
If the function is in fact linear over the region x l to x r , we obtain the root of the
function. Eventually the interval becomes so small that the linear approximation
is very good. Just like the bisection method, convergence is guaranteed so long as
your initial estimates of x l and x r bracket a zero and your function is continuous
over the range. The hope is that with an improved estimation technique, the
number of iterations required for convergence decreases.
If you need help connecting f (x l ) and f (x r ) with a straight line, have a look at
the problem statement for Example 1.7 on page 19. Let’s work out the expression
we will need to generate this new estimate. Assuming f (x l ), g (x ans ) and f (x r ) are
co-linear, where here I use g (x ans ) to designate the straight line connecting f (x l )
to f (x r ) evaluated at x ans :
f (x l ) − g (x ans ) g (x ans ) − f (x r )
=
x l − x ans
x ans − x r
At our new estimate of the root x ans , g (x ans ) = 0 so that:
f (x l )
− f (x r )
=
x l − x ans x ans − x r
Solving for x ans we find:
x ans =
x r f (x l ) − x l f (x l )
f (x l ) − f (x r )
Updating our bisection_zero_2 function for the linear interpolation method
we have:
7.17 Numerical methods: zero-finding
Listing 7.10 linear_interpol_zerp.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
% res = linear_interpol_zero(fcn_handle,xl,xr,maxit,tol)
% Function to perform linear interpolation method to find the root
% of a function (evaluated/provided in fcn_handle)
% in the range xl to xr. maxit sets the maximum number
% of iterations and tol sets the tolerance for convergence.
% Bracket is a vector where the first element is xl and the
% second element is xr.
function [res,i,fxans] = linear_interpol_zero(fcn_handle,Bracket,maxit,tol)
%hold on
% Start by un-packing Bracket into x1 and xr
xl = Bracket(1);
xr = Bracket(2);
% Check to make sure f(xl) and f(xr) have opposite signs.
% If not, don't do anything.
if fcn_handle(xl)*fcn_handle(xr) > 0
%f(xl) and f(xr) have the same sign
disp('f(xl) and f(xr) have the same sign. Exit.')
% else, f(xl) and f(xr) have the opposite sign,proceed
else
for i=1:maxit
fxl = fcn_handle(xl);
fxr = fcn_handle(xr);
% Find our new guess of the root location
xans = (xr*fxl-xl*fxr)/(fxl-fxr);
% Evaluating our funciton at this new estimate of the root
fxans = fcn_handle(xans);
% Check if we found our root
if fxans^(2) < tol^(2)
res = xans;
%i
break
elseif fxl*fxans < 0
xr = xans;
elseif fxr*fxans < 0
xl = xans;
else
disp('Critical error. Exit')
break
end
if i == maxit
disp('Reached maximum number of iterations')
xans
fxans
239
240
Chapter 7 Zero-finding
44
45
46
47
48
end
end
end
end
%plot(i,xans,'ro')
>> xl = 0;
>> xr = 20;
>> tolerance = 1e-12;
>> maxiter = 1000;
>> [d,niter,fans] = bisection_zero_2(@duck,[xl,xr],maxiter,tolerance)
d = 7.2651
niter = 51
fans = -4.5475e-13
>> [d,niter,fans] = linear_interpol_zero(@duck,[xl,xr],maxiter,tolerance)
d = 7.2651
niter = 9
fans = 0
And with just this small improvement, the number of iterations decreases from
51 to 9 while obtaining the same solution.
7.18 roots
The function fzero is very robust and can be used to find the roots of any linear
or non-linear equation. For example,
t Notice how I used a function handle to refer to MATLAB’s built-in function sin.
>> fzero(@sin,[1,4])
ans = 3.1416
π, just as we expected.
Most often, we tend to encounter polynomials. For example, cubic equations
of state, routinely used to predict the phase behavior of fluid systems, are cubic
equations. It would be great if MATLAB had a function to find all of the roots of
an equation. Otherwise, I would need to use fplot to estimate the range of each
root, and then apply fzero for each root. This was exactly the case in Example 7.1.
To find all of the roots of a polynomial, we can use MATLAB’s built-in function
roots. I stress, this is only for polynomials. Also, notice that I did not mention
that we would find all of the roots over a particular range. MATLAB will return
all of the roots; it does not allow you to specify a range. You will therefore have
to manually check which of the roots are within the desired range. With roots,
MATLAB does not use a bracketing technique. Instead, it uses algebraic tricks,
7.18 roots
241
which are beyond the scope of this class. If you are interested, have a look at the
freely available material from the textbook “Numerical Methods with Applications”
at the University of South Florida, specifically the material on the “Solution of
Quadratic Equations” followed by the “Solution of Cubic Equations.” And if you
are really interested, have a look at “Tea Time Numerical Analysis” in the Open
Texbook Library.
Imagine we have a polynomial of the form C (1)x N +C (2)x N −1 + ... +C (N )x +
C (N + 1) = 0. You need to create a vector C with all of the coefficients. Then to
find the roots, use roots(C). Let’s apply it to the 6th Chebyshev polynomial in
Example 7.1. It is a 6th order polynomial, so we can have up to 6 roots. To begin,
write your function just as you would for fzero. For this case, we have
32x 6 − 48x 4 + 18x 2 − 1
We could equivalently write this as
32x 6 + 0x 5 − 48x 4 + 0x 3 + 18x 2 + 0x − 1
(7.10)
Next, we need to create a vector. We create a (row) vector by separating the vector
elements by commas, and enclose this list with brackets. So here we would have
the following coefficient vector:
>> C = [32, 0, -48, 0, 18, 0, -1];
Finding the roots
>> Sol = roots(C)
Sol =
-0.9659
-0.7071
0.9659
0.7071
-0.2588
0.2588
Within the range 0 to 1, we have 0.9659, 0.7071, and 0.2588. This agrees perfectly
with what we found using fzero. Formally, while C is a row vector (1 × 7), Sol is a
column vector (6×1). So to select an element from Sol, we would use a command
like
>> zero_1 = Sol(3)
zero_1 = 0.9659
or equivalently
t For now, don’t worry about
the difference between a
row and column vector.
Both are vectors, so you can
select an element using a
single index. We will revisit
row and column vectors
later.
242
Chapter 7 Zero-finding
>> zero_1 = Sol(3,1)
zero_1 = 0.9659
If interested, have a look at the MATLAB documentation page “Roots of Polynomials.”
7.19 Examples
Example 7.3
Use fzero to determine the first nontrivial root of sin(x) = x 2 , where x is in radians.
(By nontrivial, I mean x > 0.)
Solution: (Link to screen cast and accompanying M-file.) We start by re-writing
the equation such that it is equal to 0, as is needed for fzero.
sin(x) − x 2 = 0
Now creating our function file:
Listing 7.11 sin_error_func.m
1 function res = sin_error_func(x)
2
res = sin(x)-x.^(2);
3 end
Before using fzero, let’s use fplot to estimate the location of the desired root. We
know that sin(x) is bound between 0 and 1. Therefore, the desired root must be
over the range 0 ≤ x ≤ 1. Use this as our bounds for the plot:
>> hold on
>> fplot(@sin_error_func,[0,1])
>> plot([0,1],[0,0],':k')
7.19 Examples
243
0.25
0.2
0.15
0.1
0.05
0
-0.05
-0.1
-0.15
-0.2
0
0.2
0.4
0.6
0.8
1
Figure 7.14 fplot(@sin_error_func,[0,1])
The non-trivial root (x 6= 0) appears to be between 0.8 and 1.0. Using fzero over
this range:
>> fzero(@sin_error_func,[0.8,1])
ans = 0.8767
Example 7.4
Use fzero to determine the real root of ln x 2 = 7. Hint: start by using fplot to
look at your function over the range 0 to 40.
Solution: (Link to screen cast and accompanying M-file.) We start by re-writing
the equation such that it is equal to 0, as is needed for fzero.
ln x 2 − 7 = 0
Now creating our function file:
Listing 7.12 log_error_func.m
1 function res = log_error_func(x)
2
res = log(x.^(2))-7;
3 end
244
Chapter 7 Zero-finding
Before using fzero, let’s use fplot to estimate the location of the desired root. It
is suggested that we look over the range 0 to 40.
>> fplot(@log_error_func,[0,40])
0
-2
-4
-6
-8
-10
-12
0
5
10
15
20
25
30
35
40
Figure 7.15 fplot(@log_error_func,[0,40])
The root appears to be between 30 and 40. Using fzero over this range:
>> fzero(@log_error_func,[30,40])
ans = 33.1155
From the plot, I might have instead estimated the lower-bound of the range to be
35. Had I done this, I would receive the following error message:
>> fzero(@log_error_func,[35,40])
Error using fzero (line 274)
The function values at the interval endpoints must differ in sign.
After our discussion of the bisection method, this error should now make more
sense to you.
7.19 Examples
245
Example 7.5
Determine the real roots of:
f (x) = −26 + 82.3x − 88x 2 + 45.4x 3 − 9x 4 + 0.65x 5
a) Use fzero to find each root. Note, we have a 5th order polynomial, so we can
have up to 5 roots.
b) Use roots to find the roots. Please first try to solve a). This way you can better
understand the difference of between fzero and roots.
Solution: (Link to screen cast and accompanying M-file.)
a) We start by re-writing the equation such that it is equal to 0, as is needed for
fzero.
0.65x 5 − 9x 4 + 45.4x 3 − 88x 2 + 82.3x − 26 = 0
Now creating our function file:
Listing 7.13 poly_error_func.m
1 function res = poly_error_func(x)
2
res = 0.65.*x.^5 - 9.*x.^4 + 45.4.*x.^3 - 88.*x.^2 + 82.3.*x - 26;
3 end
Before using fzero, let’s use fplot to estimate the location of the desired root.
Since we have a fifth order polynomial, can have up to five real roots. Since we
don’t have a good idea of the roots, let’s look at the extreme interval of –1000 to
1000. Using fplot with these bounds we have:
>> fplot(@poly_error_func,[-1000,1000])
246
Chapter 7 Zero-finding
10 14
6
4
2
0
-2
-4
-6
-1000
-500
0
500
1000
Figure 7.16 fplot(@poly_error_func,[-1000,1000])
Over this range we see that the function only crosses the x-axis once. We therefore suspect that it has only one real root. Zooming in on the range 0 to 1:
>> fplot(@poly_error_func,[0,1])
5
0
-5
-10
-15
-20
-25
0
0.2
0.4
0.6
0.8
1
Figure 7.17 fplot(@poly_error_func,[0,1])
We find that the function crosses the x-axis over this range. Using fzero to find
7.20 CPB Examples
247
the root:
>> fzero(@poly_error_func,[0,1])
ans = 0.5793
b) Next, let’s use the roots function. Remember, fzero is a robust function that
may be applied to any non-linear equation. On the other hand, roots only
applies to polynomials. The added benefit of roots is that it returns all of the
roots from one call, both real and imaginary.
We start by creating a vector of coefficients starting from the highest power in x
(the n th power) down to the 0th power. (Remember, x 0 = 1, so the 0th power is
the term without an x.)
>> C = [0.65, -9, 45.4, -88, 82.3, -26];
Now we can use the roots function which takes as an argument the vector of
coefficients.
>> roots(C)
ans =
5.5561 +
5.5561 1.0773 +
1.0773 0.5793 +
2.3338i
2.3338i
0.8606i
0.8606i
0.0000i
We have just one real root, x = 0.5793. This agrees exactly with the result of
fzero.
7.20 CPB Examples
Example 7.6
Going all the way back to Example 1.5 on page 15, you were asked to calculated the
normal boiling point of ethanol using Antoine’s equation. We did this using eq. (1.3)
where we analytically solved Antoine’s equation for T . Doing so we calculated the
normal boiling point of ethanol to be T = 78.289◦ C.
Now armed with fzero, we no longer need to perform the algebra to solve for T .
Using the original form of the Antoine equation, eq. (1.2), use fzero to calculate
the normal boiling point of ethanol. Does you answer agree with the analytic
solution?
In your error function, code in the Antoine parameters and pressure. This leaves T
as the only unknown. Also try using an anonymous function and check that you
obtain the same answer.
248
Chapter 7 Zero-finding
Solution:
All the way back in Example 1.5, we analytically solved Antoine’s equation for T and
used this to calculated the normal boiling point of ethanol. Doing so we found the
normal boiling point to be T = 78.289 ◦ C. Here, we are asked to solve numerically
using fzero. When learning a new function such as fzero, testing on problems
for which you know the answer is always a good idea.
The Antoine equation takes the form:
log10 p sat = A −
B
T +C
Next, we need to re-write Antoine’s equation as an error function suitable for use
with fzero:
log10 p sat − A +
B
=0
T +C
Now we can write our MATLAB error function. In the function file I will define
p = 760 mmHg, along with the Antoine parameters. I will minimize comments as
we have seen Antoine’s equation all too much by this point.
Listing 7.14 error_antoine.m
1 function res = error_antoine(t)
2
% Vapor pressure in mmHg
3
psat = 760;
4
% Antoine paramters, which use T in C and p in mmHg
5
a = 8.13484;
6
b = 1662.48;
7
c = 238.131;
8
% evaluating our error function
9
res = log10(psat)-a+b./(t+c);
10 end
Taking an initial guess of T = 50 ◦ C and solving:
>> tsat = fzero(@error_antoine,50)
tsat = 78.2892
A perfect match with our analytic solution! Nice! So if you encounter a challenging
problem in the future, why not give MATLAB a try? There is no need for an analytic
solution. Also, note that while here I provide an initial guess, we know physically
that Antoine’s equation is single valued. We therefore could have just specified the
entire temperature range as a bracket and would have obtained the same result.
In the interest of fun and incremental development, let’s keep building up our
example. Rather than specify the desired pressure within the error function, let’s
add it as an input variable so that the user can readily change the pressure and
compare the corresponding temperature at saturation. The challenge with doing
this is fzero expects a function with a single input and a single output variable.
7.21 Glossary
249
We can pass this additional information to the error function using an anonymous
function. Here is what that might look like.
Listing 7.15 error_antoine_tp.m
1 function res = error_antoine_tp(t,psat)
2
% Vapor pressure in mmHg
3
%psat = 760;
4
% Antoine paramters, which use T in C and p in mmHg
5
a = 8.13484;
6
b = 1662.48;
7
c = 238.131;
8
% evaluating our error function
9
res = log10(psat)-a+b./(t+c);
10 end
>> psat_mmHg = 760;
>> error_func = @(t) error_antoine_tp(t,psat_mmHg);
>> tsat = fzero(@error_func,50)
tsat = 78.2892
The same answer. Great! If you wished, you could further generalize the error
function by additionally taking your Antoine parameters as input variables.
7.21 Glossary
analytic solution: A way of solving an equation by performing algebraic operations and deriving an explicit way to compute a value that is only known
implicitly.
numerical solution: A way of solving an equation by finding a numerical value
that satisfies the equation, often approximately.
numerical method: A method (or algorithm) for generating a numerical solution.
map: A correspondence between the elements of one set (the range) and the
elements of another (the domain). You can think of sequences, vectors and
functions as different kinds of maps.
range: The set of values a map maps from.
domain: The set of values a map maps to.
discrete set: A set, like the integers, whose elements are countable.
continuous set: A set, like the real numbers, whose elements are not countable.
You can think of floating-point numbers as a continuous set.
250
Chapter 7 Zero-finding
zero (of a function): A value in the range of a function that maps to 0.
function handle: In MATLAB, a function handle is a way of referring to a function
by name (and passing it as an argument) without calling it.
shadow: A kind of name collision in which a new definition causes an existing
definition to become invisible. In MATLAB, variable names can shadow
built-in functions (with hilarious results).
anonymous function: A function that is not stored in a file, but is instead stored
as a variable (a function_handle). It behaves just like a function stored in
a file, except it can only contain a single executable statement.
MAT-file: A file containing MATLAB formatted data. We can load data from or
write date to these files using the load and save functions, respectively.
7.22 Exercises
251
7.22 Exercises
Exercise 7.1 Air under normal conditions flows through a 4 mm diameter pipe with an
average velocity of V = 60 m/s. We wish to determine the pressure drop in a 0.2 m section
of the pipe.
For air under normal conditions, the density and viscosity can be taken to be ρ = 1.23
kg/m3 and µ = 1.79 × 10−5 N·s/m2 . The pressure drop (∆p) may be computed as
∆p = f
l 1
ρV 2
D2
(7.11)
where l is the length of the pipe, D is the pipe diameter, ρ is the density of the fluid (air),
V is the fluid (air) velocity, and f is the friction factor. For this problem we have turbulant
flow. The turbulant portion of the Moody chart is represented by the Colebrook formula
Ã
!
2.51
1
²/D
+ p
(7.12)
p = −2 log10
3.7 Re f
f
where ² is the pipe roughness, and Re is the Reynold’s number, defined as
Re =
ρV D
µ
(7.13)
Compute the pressure drop for the following two cases, and comment on the effect
of the pipe material:
1. Cast iron pipe, ² = 0.26 mm
2. Drawn tubing, ² = 0.0015 mm
Note that I find the pressure drop is 9.1160 kPa for cast iron, and 3.0864 kPa for drawn
tubing. Use these values to debug your code.
Exercise 7.2 Building upon Exercise 7.2, it is common that you measure the pressure
drop via a manometer and use this to calculate the velocity (or in general the flow rate) of
the fluid. I will recast Exercise 7.2 as such a problem.
Air under normal conditions flows through a 4 mm diameter pipe. Over a 0.2 m
section of pipe we measure that the pressure drop, ∆p, is 50 kPa. Knowing this we wish to
determine the average velocity of the flowing air.
For air under normal conditions, the density and viscosity can be taken to be ρ = 1.23
kg/m3 and µ = 1.79 × 10−5 N·s/m2 . The pressure drop (∆p) may be computed as
∆p = f
l 1
ρV 2
D2
(7.14)
where l is the length of the pipe, D is the pipe diameter, ρ is the density of the fluid (air),
V is the fluid (air) velocity, and f is the friction factor. For this problem we have turbulant
flow. The turbulant portion of the Moody chart is represented by the Colebrook formula
Ã
!
1
²/D
2.51
+ p
(7.15)
p = −2 log10
3.7 Re f
f
where ² is the pipe roughness, and Re is the Reynold’s number, defined as
Re =
ρV D
µ
(7.16)
252
Chapter 7 Zero-finding
Compute the average velocity for the following two cases, and comment on the effect
of the pipe material:
1. Cast iron pipe, ² = 0.26 mm
2. Drawn tubing, ² = 0.0015 mm
Note that in reality, when modelling compressible fluids such as air, it is important to
account for the temperature and pressure dependence of ρ and µ. It is ignored here for
simplicity. For the case of the cast iron pipe, I compute a velocity of 141.1115 m/s, and for
drawn tubing I compute a velocity of 281.0068 m/s.
Exercise 7.3 Have you ever heard of a Galilean thermometer1 ? It is a clever thermometer
invented in the 1660s. In fact, you can still find Galilean thermometers available for
purchase today, although today they are mostly used for decoration. The basis of the
thermometer is that the density of a liquid changes with temperature, which in turn
would effect the buoyancy of an object in that liquid. While we will not exactly model a
Galilean thermometer, we will model something similar.
We will model the buoyancy of a spherical object of radius r o and density ρ o . The
volume of a sphere with radius r o is 4/3πr o3 . If a sphere with radius r o is submerged in a
liquid to a depth d , the volume (V ) of the sphere below the liquid line (or equivalently
the volume of displaced liquid) is:
V=
¢
π¡
3r o d 2 − d 3
3
as long as d < 2r ; that is, so long as the object is not submerged! An object floats at the
level where the weight of the displaced liquid equals the total weight of the object. The
weight of the object (Wo ) and the weight of the displaced liquid (Wl ) may be computed
as:
4
Wo = ρ o g Vo = ρ o g πr o3
3
Wl = ρ l g V = ρ l g
¢
π¡
3r o d 2 − d 3
3
where g is the gravitational constant. Equating we have:
¢
4
π¡
ρ o g πr o3 = ρ l g
3r o d 2 − d 3
3
3
And simplifying:
¡
¢
4ρ o r o3 = ρ l 3r o d 2 − d 3
(7.17)
Okay, now we need some parameters. The spherical object can be assumed to have a
constant density of ρ o = 0.3 g/mL, and have a radius of r o = 100 cm. We will model the
liquid as ethanol, for which the density is described by the following equation2 :
n
ρ l = A · B −(1−T /C )
(7.18)
1 https://en.wikipedia.org/wiki/Galileo_thermometer
2 Yaws, Carl L. (2012; 2013; 2014). Yaws’ Critical Property Data for Chemical Engineers
and Chemists. Knovel. Retrieved from https://app.knovel.com/hotlink/toc/id:kpYCPDCECD/
yaws-critical-property/yaws-critical-property
7.22 Exercises
253
where T is the temperature in K, ρ l is the liquid density in g/mL, and A = 0.276, B =
0.27668, C = 516.25, and n = 0.2367 are constants. This expression is valid over the
range 159.05 to 516.25 K. As a note to make sure you are correctly calculating the density, at 298.15 K we have ρ l = 0.787 g/mL. The buoyancy of the object will change with
temperature. If you measure the depth of the object, d , you can find the temperature.
Cool!
Before moving on, let’s pause and make a note about units. It is all about being
consistent. In equation 7.17, use the same units for ρ o and ρ l as they will cancel out. For
both you are provided with units of g/mL, so just use this. The units of r o and d will be
the same. So if you use units of cm for r o (which you should), then d will be in units of
cm. My last note is in equation 7.18, T is in K.
Write a function that takes as an input the depth (d ) in cm and returns the temperature in ◦ C. To check that your code is working correctly, for d = 180 cm, I obtain
243.0829 ◦ C. For d = 80 cm, I obtain –54.1430 ◦ C. Please take your time and check your
code for correctness. As a final note, solving using fzero I use a search range of 159.05
to 516.25 K. If you encounter a temperature greater than 516.25 K, you will encounter a
numerical error.
Exercise 7.4 In the separation of binary mixtures via distillation, the presence of an
azeotrope puts a limit on the achievable separation. For a binary system at vapor/liquid
equilibrium, assuming the vapor phase is an ideal gas and the Poynting correction is
negligible, the relative volatility (α) takes the form
α (T, x 1 ) =
γ1 (T, x 1 ) p 1sat (T )
γ2 (T, x 1 ) p 2sat (T )
where x 1 is the mole fraction of component 1 (x 1 + x 2 = 1), T is the temperature, γ1 and
γ2 are the activity coefficient of component 1 and 2, respectively, and p 1sat and p 2sat are
the pure component vapor pressure of component 1 and 2, respectively. Component 1
corresponds to the most volatile component (MVC) and component 2 is the least volatile
component (LVC). In the design of vapor/liquid separation processes (i.e., distillation),
the greater the deviation of α from unity the easier the separation. When α > 1 the MVC is
concentrated in the vapor phase, when α < 1 the LVC is concentrated in the vapor phase,
and when α = 1 we have an azeotrope, no separation is possible.
Next, we will adopt the standard assumption that γ1 and γ2 are independent of pressure, and will consider only isothermal (constant T ) vapor/liquid equilibrium (P x y). For
this case, α is a function of just a single variable, x 1 . At 313.15 K, the binary system ethyl
acetate(1)/ethanol(2) exhibits an azeotrope. I would like you to solve for the composition
of the azeotrope. Put differently, what value of x 1 results in α = 1? Next, I will provide you
with some additional information to solve the problem.
The vapor pressure of ethyl acetate (p 1sat ) and ethanol (p 2sat ) can be computed using
Antoine’s equation:
log10 p sat = A −
B
T +C
where A, B , and C are constants, p sat is the vapor pressure in units of mmHg, and T is
the temperature in ◦ C. In the table below, I provide parameters along with the minimum
(Tmin ) and maximum (Tmax ) temperature for which use of the parameters is appropriate.
The parameters are all adopted from those in the freely available Dortmund Data Bank.3
3 http://ddbonline.ddbst.com/AntoineCalculation/AntoineCalculationCGI.exe
254
Chapter 7 Zero-finding
Note that the Dortmund Data Bank also provides an online calculator you can use to
make sure your Antoine functions are working correctly.
compound
ethyl acetate
ethanol
A
7.10179
8.20417
B
1244.95
1642.89
Tmin [◦ C]
16
–57
C
217.9
230.3
Tmax [[◦ C]
76
80
The activity coefficient can be modeled using the NRTL (non-random two-liquid)
equation:
·
µ
ln γ1 = x 22 τ21
G 21
x 1 + x 2G 21
¶2
·
µ
ln γ2 = x 12 τ12
G 12
x 2 + x 1G 12
¶2
+
+
τ12G 12
¸
(x 2 + x 1G 12 )2
τ21G 21
¸
(x 1 + x 2G 21 )2
where
τ12 =
g 12 − g 22
g 21 − g 11
τ21 =
RT
RT
and
G 12 = exp (−α12 τ12 ) G 21 = exp (−α21 τ21 )
where R is the molar gas constant and T is the absolute temperature. Note that the
terms τ12 and τ21 are dimensionless. In theory, the parameter g i j is an energy parameter
characteristic of the i - j molecular interaction, and αi j is the non-randomness parameter
corresponding to the inverse of the coordination number. Know that α12 and α21 , the
non-randomness parameters, are different than the relative volatility, α. We will use the
following set of parameters that were regressed by students in my chemical engineering
thermodynamics class during fall of 2018 using freely available reference data from the
Dortmund Data Bank.4
α12 = α21 = 0.296
g 12 − g 22 = 72.347 J/mol g 21 − g 11 = 3682.7378 J/mol
If you wished to compare values of α from the reference data, they would be computed
as:
α=
y 1 /x 1 y 1 (1 − x 1 )
=
y 2 /x 2 y 2 (1 − x 2 )
Using the parameters provided and a molar gas constant of R = 8.314 J/(mol K), I obtain
x 1 = 0.6092. Use this to help you debug your code... if necessary.
4 http://www.ddbst.com/en/EED/VLE/VLE%20Ethanol%3BEthyl%20acetate.php
7.22 Exercises
Exercise 7.5 In Example 5.10 we used the ideal gas equation of state and the truncated
virial equation with corresponding states theory to estimate the molar volume of n-butane
at 350 K and 2 bar. Using the truncated virial equation, we computed the compressibility,
Z , and then calculated the molar volume as v = Z v ig , where v ig is the molar volume of
an ideal gas at the same temperature and pressure.
Since our function for the truncated virial (v_virial) is dependent on our function
for the molar volume of an ideal gas (v_ideal_gas), in Section 6.1 we decided to combine
the functions into a single file, where v_virial is the top function and v_ideal_gas is
the helper function. Then in Exercise 6.4 we vectorized our code. Let’s keep the fun going
here!
So far we have used the truncated virial equation in its natural form, an expression
explicit in molar volume (or equivalently the compressibility, Z ). Knowing the pure
component constants, Tc , p c , and ω, and specifying the temperature and pressure of
interest, the molar volume is readily computed.
But what if the pressure and molar volume were specified, and you were asked to
solve for the temperature? While I would not attempt to solve this problem analytically,
now using fzero this is entirely possible. Let’s give it a try!
Recall that from the NIST WebBook5 for n-butane we have: Tc = 425.125 K, p c =
37.960 bar, and ω = 0.201. For a pressure of p = 2 bar and molar volume of v = 1 × 104
cm3 /mol, what is the temperature? You must use fzero in your solution. To check the
reasonableness of your solution, have a look at Figure 6.3 in the solution manual.
5 http://webbook.nist.gov/chemistry/fluid/
255
Chapter
8
Systems of Equations
In Chapter 7 we learned how to use MATLAB for zero-finding and to find the
roots of polynomials. In Chapter 8 we continue and solve systems of linear and
non-linear equations. By the end of this chapter you will be able to:
• Exhibit ability to construct vectorized “error” functions for use with fsolve
• Apply fsolve to solve systems of nonlinear equations
• Apply rref to solve a system of linear equations
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
8.1 Matrices
A matrix is a two-dimensional version of a vector. Like a vector, it contains
elements that are identified by indices. The difference is that the elements are
arranged in rows and columns, so it takes two indices to identify an element.
We have already seen a number of ways to create a matrix of a given size (see
Section 4.13 on page 97); okay, we created vectors, but a vector is a matrix. To
create a matrix in which all entries are 1, we can use the built-in ones function.
We have used this function so far as ones(1,N) which creates a vector of length
N. The first entry (1) actually corresponds to the number of rows and the second
entry corresponds to the number of columns (N). What we have been calling a
vector in MATLAB you can equally call a “row” vector; it is a single rowed matrix.
You could also create a “column” vector as ones(N,1). Another common usage is
ones(N) creates a square matrix (i.e., same number of columns and rows) with
N columns and rows. We have also used the built-in function zeros. It behaves
just like ones, only every entry is assigned a value of 0. The built-in function
rand takes the same form too. With rand, each element is a uniformly distributed
pseudo-random number between 0 and 1; put simply, you can think of it as
random number between 0 and 1.
257
258
Chapter 8 Systems of Equations
Sometimes you may find you wish to create a matrix of ones or zeros which is
the same size as a matrix which is already defined in your workspace. You can do
this quite easily. Say matrix A is defined in your workspace. Then size(A) returns
the size (or dimensions) of matrix A. You then need just pass this result to ones or
zeros. Or combine the statements as ones(size(A)).
Another of many ways to create a matrix is the magic function, which returns
a “magic” square matrix (n by n) with the given size:
>> M = magic(3)
M = 8
1
3
5
4
9
6
7
2
If you don’t know the size of a matrix, you can use whos to display it:
>> whos
Name
M
Size
3x3
Bytes
72
Class
double
Attributes
Or the size function, which returns a vector:
>> V = size(M)
V = 3
3
The first element is the number of rows, the second is the number of columns.
To read an element of a matrix, you specify the row and column number(s):
>> M(1,2)
ans = 1
>> M(2,1)
ans = 3
When you are working with matrices, it takes some effort to remember which
index comes first, row or column. I find it useful to repeat “row, column” to myself,
like a mantra. You might also find it helpful to remember “down, across,” or the
abbreviation RC.
Another way to create a matrix is to enclose the elements in brackets, with
semi-colons between rows:
>> D = [1,2,3 ; 4,5,6]
D = 1
2
3
4
5
6
8.1 Matrices
>> size(D)
ans = 2
259
3
As you begin to work with row and column vectors, and matrices, remember our discussion of incremental development (Section 2.8) and unit testing
(Section 2.9) from Chapter 2. Practice creating matrices and referring to specific
elements in the Command Window to test your understanding. To be honest, this
is still something I often do when writing code; sometimes there is too much to
remember, so leverage the interactive abilities of MATLAB.
Something we will find very useful in the future is colon notation. It will
be given much more attention later, but I will introduce it here while we are
discussing matrices. Many times we will want to isolate all of the elements of
a specific row or column in a matrix. If I look at D like a table, perhaps row 1
contains data for a specific time at which a measurement was made. We can
isolate this row using colon notation:
>> D1 = D(1,:)
D1 = 1
2
3
We can interpret (1,:) as row 1, all columns. You could likewise use it to isolate
a specific column. To isolate just the second column, we will use (:,2) meaning
all rows, second column:
>> D2 = D(:,2)
D2 = 2
5
which is a column vector, which we will discuss momentarily. You may also remember from our discussion of for loops that colon notation can be used to
create a vector, and we then later saw how vectors of indices (think of the find
command) can be used to isolate elements of a vector. Let’s put it together:
>> Dr = D(:,2:3)
Dr = 2
3
5
6
Is the result what you expected? While we could spend much more time on this,
let me make just one more point. We see that the use of comma notation is
very useful for isolating specific parts of a matrix. This can be useful to perform
operations (+, –, .*, ./, etc.), logical comparisons, and to apply functions
such as find only to a specific part or range of a matrix. This also gives us a way to
use length, which only applies to vectors, to find the number of rows or columns.
(But size is just as easy.)
260
Chapter 8 Systems of Equations
8.2 Row and column vectors
Although it is useful to think in terms of scalars, vectors and matrices, from MATLAB’s point of view, everything is a matrix. A scalar is just a matrix that happens
to have one row and one column:
>> x = 5;
>> size(x)
ans = 1
1
And a vector is a matrix with only one row:
>> R = 1:5;
>> size(R)
ans = 1
5
Well, some vectors, anyway. Actually, as we have already mentioned, there are
two kinds of vectors. The ones we have seen so far are called row vectors, because
the elements are arranged in a row; the other kind are column vectors, where the
elements are in a single column.
One way to create a column vector is to create a matrix with only one element
per row:
>> C = [1;2;3]
C =
1
2
3
>> size(C)
ans = 3
1
And since C is a vector, we can use the length command too:
>> length(C)
ans = 3
The difference between row and column vectors is important in linear algebra,
but for most basic vector operations, it doesn’t matter. When you index the elements of a vector, you don’t have to know what kind it is:
>> R(2)
ans = 2
8.3 The transpose operator
261
>> C(2)
ans = 2
8.3 The transpose operator
The transpose operator, which looks remarkably like an apostrophe, computes
the transpose of a matrix, which is a new matrix that has all of the elements of
the original, but with each row transformed into a column (or you can think of it
the other way around).
In this example:
>> D = [1,2,3 ; 4,5,6]
D = 1
2
3
4
5
6
D has two rows, so its transpose has two columns:
>> Dt = D'
Dt = 1
4
2
5
3
6
What effect does the transpose operator have on row vectors, column vectors,
and scalars? Let’s start with row vectors. We will define a row vector and then
compute the transpose.
>> X = 1:5
X = 1
2
3
>> Xt = X'
Xt = 1
2
3
4
5
>> size(X)
ans = 1
5
>> size(Xt)
ans = 5
1
4
5
262
Chapter 8 Systems of Equations
So the transpose of a row vector is a column vector. Likewise, the transpose of
a column vector is a row vector. If we take the transpose of Xt, we will get back to X:
>> Xt'
ans = 1
2
3
4
5
As for a scalar, which is a 1 by 1 matrix, it is unchanged by the transpose operator.
8.4 Zero-finding: what we’ve done so far
In Chapter 7 we learned to use fzero. fzero is a built-in MATLAB function that
combines the best features of several efficient and robust numerical methods.
Specifically, fzero employs a bracketing method; so long as the function is continuous and changes signs over the interval, then a zero exist and fzero will be
successful. fzero is highly robust, and should always be your first choice for root
finding.
However, fzero has three limitations. 1) If a functions has more than one
zero over the range of interest, MATLAB will return the location of only one of the
zeroes. To overcome this, we saw how we could use fplot to improve the search
range, and then we could call fzero multiple times with different search ranges.
Alternatively, for the special case of a polynomial, we found we could use roots
to obtain all of the roots of the polynomial.
2) Second is the restriction that the function needs to change signs over
the interval. In general, this is not a problem. However, imagine the case of
(x − 2)2 = 0. This equation has a zero at x = 2. However, fzero will not work.
The problem is that for all values of x other than x = 2, the function is positive.
The function therefore does not change signs over the interval. One way to
overcome this shortcoming is to expand (x − 2)2 and then use roots. Another
will be fsolve, which we will introduce momentarily.
3) Third and most important, at least as far as this chapter is concerned, is
that fzero applies to a single equation containing a single unknown variable.
Here we will learn about fsolve which can be applied to a system of equations.
fsolve can be applied to a single equation with a single unknown, but for that
case fzero should always be your first choice.
8.5 System of equations
Imagine that you encounter the following equations:
y = −x − 3
x 2 + y 2 = 17
8.5 System of equations
263
At first glance you might think you could solve these equations by calling fzero
once to solve for x and once to solve for y. The problem is that each equation
involves both variables, which is what makes this a system of equations and
not just a list of unrelated equations. To solve a system, you have to solve the
equations simultaneously. Fortunately, MATLAB has a built-in function fsolve
that can handle systems of equations.
Working with fsolve is analogous to fzero. Let us therefore start by finding
the zeros of a single equation. For this case, the use of fsolve will look exactly
like fzero. Imagine we would like to solve the equation x 2 − 2x = 3. We begin by
writing an error function, such that the function evaluates to zero at a solution:
x 2 − 2x − 3 = 0.
Listing 8.1 error_func.m
1 function res = error_func(X)
2
res = X.^(2)-2.*X-3;
3 end
Let’s assume we are searching for the root over the range 0 < x < 5. Solving using
fzero:
>> fzero(@error_func,[0,5])
ans = 3
With fzero we could alternatively provide an initial estimate of the zero location
instead of a range to search.
>> fzero(@error_func,4)
ans = 3.0000
Solving using fsolve, the same error function is needed. The only difference
for this case is you can only provide an initial estimate of the zero location (and
not a range).
>> fsolve(@error_func,4)
ans = 3.0000
A perfect match! Note that with this call you would also receive the following
information:
Equation solved.
fsolve completed because the vector of function values is near zero
as measured by the default value of the function tolerance, and
the problem appears regular as measured by the gradient.
<stopping criteria details>
What is most important is the top line, “Equation solved”. A discussion of the
t Also, while not discussed
here, fsolve can be applied to non-square systems
of equations; that is, where
the number of equations
is greater than the number
of unknowns, or when the
number of unknowns is
greater than the number of
equations. For these cases,
fsolve acts as a minimizer.
I hope to add a chapter on
the topic in the future.
t Note that fsolve is part of
MATLAB’s Optimization
Toolbox. If you obtain an
error message indicating
that the command fsolve
is unknown, you likely did
not include the Optimization Toolbox in your initial
MATLAB installation.
264
Chapter 8 Systems of Equations
theory behind fsolve is beyond the scope of this class, so we will not worry
about the rest of the message here. To learn more about fsolve, have a look at
the MATLAB documentation. In Example 8.2 we will look at how the options used
by fsolve can be updated, including changing the tolerance and suppressing the
additional information displayed with the solution (i.e., “Equation solved”).
The major reason for using fsolve over fzero is that it can be used to solve
systems of equations. Using fsolve to solve a system of equations is very similar
to solving a single equation. We will need to create an error function. Just as with
fzero and the single equation case with fsolve, the error function will have just
a single input and a single output variable. The input variable will be a vector
containing the current estimate/value of the variable(s) you are solving for, and
the output will be a vector containing the value of the error function for each
equation in the system.
Let’s consider the system of equations we started our discussion with. We first
re-write our equations as a system of error functions.
y +x +3 = 0
x 2 + y 2 − 17 = 0
Next, write our error function M-file. Just as before, fsolve expects that our
function has a single input variable. Here the input variable will be a vector of
length 2. The first element will be x and the second element will be y. Note that
you can list the variables in whatever order you would like, you just need to make
sure you are consistent throughout the problem.
Listing 8.2 error_function_series.m
1 function res = error_function_series(X)
2
% Un-packing my input vector
3
x = X(1);
4
y = X(2);
5
% Our system of equations
6
res1 = y+x+3;
7
res2 = x^(2)+y^(2)-17;
8
% Packing up my result
9
res = [res1,res2];
10 end
The input variable is a vector with two elements, the first element is x and the
second element is y. I gave it a capital letter to remind me that it is a vector. The
body of the function includes three paragraphs, each explained by a comment.
The first paragraph unpacks the vector by copying the elements into scalar
variables. This isn’t necessary, but giving names to these values helps me remember what’s what. It also makes the second paragraph, where we compute the
error functions, resemble the mathematical equations we were given, which helps
prevent errors.
8.6 What can go wrong?
The last paragraph packs the computed error functions back into a vector.
When fsolve calls this function, it provides a vector as input and expects to get a
vector as output.
Calling fsolve from the Command Window will look identical as before. Only
since we now have a system of equations, we need to provide an initial estimate
of each variable. This problem can be solved analytically to find that x = −4 and
y = 1 is a solution, as is x = 1 and y = −4. Solving with fsolve:
>> x0 = 0;
>> y0 = -5;
>> X0 = [x0,y0]
>> Sol = fsolve(@error_function_series,X0)
Sol = 1.0000
-4.0000
Nice! The solution is a vector of length 2. The first element corresponds to x, and
the second element corresponds to y. The order of this vector agrees exactly with
the order of your vector with the initial guesses and the input vector to your error
function M-file.
>> x = Sol(1);
>> y = Sol(2);
Just as with fzero, we find that fsolve returns a single zero location. To find
the other we need to start with a different set of initial guesses.
>> x0 = -5;
>> y0 = 0;
>> X0 = [x0,y0]
>> Sol = fsolve(@error_function_series,X0)
Sol = -4.0000
1.0000
8.6 What can go wrong?
Let’s re-visit our previous example but try an initial guess where x = y.
>> x0 = 2;
>> y0 = 2;
>> X0 = [x0,y0];
>> Sol = fsolve(@error_function_series,X0)
Sol = 2.7803
2.7803
and if were were to display the full output, we would see:
265
266
Chapter 8 Systems of Equations
No solution found.
fsolve stopped because the last step was ineffective. However,
the vector of function values is not near zero, as measured by
the default value of the function tolerance.
<stopping criteria details>
So MATLAB returns a solution, but it is not correct. You can confirm this by
passing the result to the error function:
>> error_function_series(Sol)
ans = 8.5607
-1.5395
In this particular case, all we need to do is update the initial guess so that x 6= y.
>> x0 = 1;
>> y0 = 2;
>> X0 = [x0,y0]
>> Sol = fsolve(@error_function_series,X0)
Sol = -4.0000
1.0000
So we find that the success of fsolve is sensitive to the initial estimate of the
location of the zeroes. In general, the better the estimate, the better off you will
be. If the system of equations corresponds to a physical problem, we can typically
provide a reasonable initial estimate. For example, if I were solving for mole
fraction compositions, I know they will be between 0 and 1. So 0.5 might be a
good initial guess. This isn’t always the case, and we will see in the Examples
how rand can be used. This also brings us back to the point of using fzero over
fsolve. If you have a single equation with a single unknown that changes sign
over the interval of interest, use fzero; it will be guaranteed to work.
Additionally, this leads to the question if we were using fsolve as part of a
larger script or function, how can we check that it worked without looking at the
resulting message? In Section 6.4 we saw how we could write our functions to
return multiple/optional output variables. This is how most of MATLAB’s built-in
functions work. The first output variable provided by fsolve is the estimate of
the solution to the system of equations. The second (optional) variable is the
value of the system of error functions using the estimated solutions. And the
third (optional) variable is the exit flag. A value of 1, 2, 3 or 4 all mean “Equation
solved;” it worked! A value of 0 means it did not work because the number of
iterations were exceeded. Increase the number of iterations and maybe you will
be successful. And a negative value means... bad news, it didn’t work. Let’s revisit
our last two calls with our new tricks.
8.6 What can go wrong?
>> x0 = 2;
>> y0 = 2;
>> X0 = [x0,y0]
>> [Sol,fun_val,flag] = fsolve(@error_function_series,X0)
Sol = 2.7803
2.7803
fun_val = 8.5607
-1.5395
flag = -2
The value of fun_val agrees with what we computed previously, and since the
exit flag is not –2, the result is not reliable.
>> x0 = 1;
>> y0 = 2;
>> X0 = [x0,y0]
>> [Sol,fun_val,flag] = fsolve(@error_function_series,X0)
Sol = -4.0000
1.0000
fun_val =
1.0e-12 *
0.0018
0.3766
flag = 1
Nice! Remember MATLAB is using a default tolerance to determine if the solution
is close enough to zero. While not exactly zero here, the values are very small.
For more details on the working of fsolve and the values of the exit flag, have
a look at the documentation page. Below is screenshot from the documentation
page describing the three output variables just discussed.
Figure 8.1 Snippet of the documentation page for fsolve that describes
the first three output variables.
267
268
Chapter 8 Systems of Equations
8.7 Examples
Example 8.1
Solve the following system of equations:
1
y = x −5
2
y = x 2 + 2x − 15
There are two unique solutions. Please find both.
Solution: (Link to screen cast and accompanying M-file.) What fun, a system
of non-linear equations. Let’s begin by solving by hand. Let’s subtract the first
equation from the second. This kills y and results in:
3
x 2 + x − 10 = 0
2
We have an equation of the form ax 2 + bx + c = 0, so let’s go ahead and use the
quadratic formula:
x=
p
−b ± b 2 − 4ac
2a
To help, I wrote a MATLAB function to perform the calculation for me.
Listing 8.3 quadratic_eq.m
1 function res = quadratic_eq(a,b,c)
2
sqrtt = sqrt(b^(2)-4*a*c);
3
root1 = (-b+sqrtt)/(2*a);
4
root2 = (-b-sqrtt)/(2*a);
5
res = [root1,root2];
6 end
Running, and then using the result to solve for y:
>> X = quadratic_eq(1,1.5,-10)
X = 2.5000
-4.0000
>> Y = 0.5*X - 5
Y = -3.7500
-7.0000
In the interest of more practice, let’s go ahead and use roots and confirm we get
the same answer:
8.7 Examples
269
>> C = [1, 3/2, -10];
>> X = roots(C)
X = -4.0000
2.5000
>> Y = 0.5*X - 5
Y = -7.0000
-3.7500
Now let’s solve numerically using fsolve. We begin by writing our equations so
that they evaluate to zero at the solution:
1
y − x +5 = 0
2
y − x 2 − 2x + 15 = 0
We can now write our error function.
Listing 8.4 error_ex1.m
1 function res = error_ex1(X)
2
% Start by unpacking our input vector. I will take the first
3
% entry to be y and the second entry to be x.
4
y = X(1);
5
x = X(2);
6
7
% Our system of equations
8
res1 = y - 0.5*x + 5;
9
res2 = y - x^(2) - 2*x + 15;
10
11
% Pack-up it up
12
res = [res1,res2];
13 end
Next we can solve in the Command Window . Remember, we will need to provide
an initial estimate of the solution. Rather than guess, let’s see what happens if we
use the random number generator to generate them for us. I will generate initial
guesses over the range –5 to 5.
>> fsolve(@error_ex1,10*rand(1,2)-5)
ans = -3.7500
2.5000
>> fsolve(@error_ex1,10*rand(1,2)-5)
ans = -3.7500
2.5000
>> fsolve(@error_ex1,10*rand(1,2)-5)
ans = -3.7500
2.5000
270
Chapter 8 Systems of Equations
>> fsolve(@error_ex1,10*rand(1,2)-5)
ans = -7.0000
-4.0000
There we have it! We were able to get both sets of solutions. Remember, the way
I set-up my error function, the first value is y followed be x. The results agree
perfectly with what we found by hand.
Example 8.2
Solve the following system of equations:
xy = 1
x+y =2
Solution: (Link to screen cast and accompanying M-file.) Here there is only one
unique solution.
By inspection, we know the solution will be x = y = 1. Let’s see how fsolve does.
Let’s re-write our equations so that they equal zero, then create our error function.
xy −1 = 0
x + y −2 = 0
Listing 8.5 error_ex2.m
1 function res = error_ex2(X)
2
% Unpack! Let the first entry be y and the second x
3
y = X(1);
4
x = X(2);
5
6
% Our system of equations
7
res1 = x*y-1;
8
res2 = x+y-2;
9
10
% Pack it up!
11
res = [res1,res2];
12 end
Just as in the last problem, I will solve by taking an initial guess of my solutions
using the random number generator with values between –5 and 5.
>> fsolve(@error_ex2,10*rand(1,2)-5)
ans = 0.9994
1.0006
8.7 Examples
271
The numeric answer is not exactly x = y = 1. We can confirm by passing the result
to our error function.
>> error_ex2(ans)
ans =
1.0e-06 *
-0.3326
-0.0000
It’s not quite zero, but it is very close, and likely close enough for most applications.
In the interest of having fun unit testing, if you wished to improve the accuracy of
the calculation, this is a matter of the tolerance used by fsolve to determine if it
is close enough. You can have a look at MATLAB’s documentation, but the default
tolerances are 1 × 10−6 . Let’s make them smaller and see what happens. To change
the options, have a look at the documentation page on “optimoptions” and the
options table in the documentation page for fsolve. Since we will have an interest
in changing the tolerance, also see the documentation page on “Tolerances and
Stopping Criteria.”
Let’s begin by displaying the current, default options:
>> options = optimoptions('fsolve')
options =
fsolve options:
Options used by current Algorithm ('trust-region-dogleg'):
(Other available algorithms: 'levenberg-marquardt', 'trust-region')
Set properties:
No options set.
Default properties:
Algorithm:
CheckGradients:
Display:
FiniteDifferenceStepSize:
FiniteDifferenceType:
FunctionTolerance:
MaxFunctionEvaluations:
MaxIterations:
OptimalityTolerance:
OutputFcn:
PlotFcn:
SpecifyObjectiveGradient:
StepTolerance:
TypicalX:
UseParallel:
'trust-region-dogleg'
0
'final'
'sqrt(eps)'
'forward'
1.0000e-06
'100*numberOfVariables'
400
1.0000e-06
[]
[]
0
1.0000e-06
'ones(numberOfVariables,1)'
0
Show options not used by current Algorithm ('trust-region-dogleg')
You can match up all of the options under Default properties in the options
272
Chapter 8 Systems of Equations
table in the fsolve documentation. Here, we update FunctionTolerance and
OptimalityTolerance, and store the new set of options to a new variable, options2.
While you can and normally would suppress the output, I will display it here so
you can see what has changed:
>> options2 = optimoptions('fsolve','FunctionTolerance',1e-12,...
'OptimalityTolerance',1e-12)
options2 =
fsolve options:
Options used by current Algorithm ('trust-region-dogleg'):
(Other available algorithms: 'levenberg-marquardt', 'trust-region')
Set properties:
FunctionTolerance: 1.0000e-12
OptimalityTolerance: 1.0000e-12
Default properties:
Algorithm:
CheckGradients:
Display:
FiniteDifferenceStepSize:
FiniteDifferenceType:
MaxFunctionEvaluations:
MaxIterations:
OutputFcn:
PlotFcn:
SpecifyObjectiveGradient:
StepTolerance:
TypicalX:
UseParallel:
'trust-region-dogleg'
0
'final'
'sqrt(eps)'
'forward'
'100*numberOfVariables'
400
[]
[]
0
1.0000e-06
'ones(numberOfVariables,1)'
0
Show options not used by current Algorithm ('trust-region-dogleg')
Now when we call fsolve we can provide an additional input argument corresponding to our set of options. I will use both the initial, default options we stored
to options, and then our updated set in options2.
>> sol = fsolve(@error_ex2,10*rand(1,2)-5,options)
sol = 0.9994
1.0006
>> sol2 = fsolve(@error_ex2,10*rand(1,2)-5,options2)
sol2 = 1.0000
1.0000
Nice! What if we wanted to update options2 to also not display the message with
the solution? The relevant option is Display, and we can change its value as:
8.8 One final fsolve note
273
>> options2 = optimoptions(options2,'Display','none')
options2 =
fsolve options:
Options used by current Algorithm ('trust-region-dogleg'):
(Other available algorithms: 'levenberg-marquardt', 'trust-region')
Set properties:
Display: 'none'
FunctionTolerance: 1.0000e-12
OptimalityTolerance: 1.0000e-12
Default properties:
Algorithm:
CheckGradients:
FiniteDifferenceStepSize:
FiniteDifferenceType:
MaxFunctionEvaluations:
MaxIterations:
OutputFcn:
PlotFcn:
SpecifyObjectiveGradient:
StepTolerance:
TypicalX:
UseParallel:
'trust-region-dogleg'
0
'sqrt(eps)'
'forward'
'100*numberOfVariables'
400
[]
[]
0
1.0000e-06
'ones(numberOfVariables,1)'
0
Show options not used by current Algorithm ('trust-region-dogleg')
And last, if you wanted to reset options2 to contain the default values, we need
just go back to our starting command: options2 = optimoptions(’fsolve’).
If the options used here do not work for you, have a look at the documentation page
“Current and Legacy Option Name Tables.” Many of the option names changed in
MATLAB R2016a. If you are using GNU Octave, consult its documentation.
t By later, these are topics I
8.8 One final fsolve note
In this chapter we were introduced fsolve to solve systems of equations. We
considered problems where the system of equation was square, i.e., the number
of equations was equal to the number of unknowns. We will see later that fsolve
also works when the system of equations is not square, where either the number
of equations is greater than the number of unknowns (as is the case in data fitting)
or when the number of equations is less than the number of unknowns (as in
the case of optimization/minimization). Also, while fsolve can be used to solve
systems of linear and non-linear equations, the case of linear equations is unique
in that they can readily be solved exactly. We will consider this special case next.
hope to add to the text in
the near future.
274
Chapter 8 Systems of Equations
8.9 System of linear equations
In Section 8.4 we found the roots of the equation x 2 − 2x = 3. This corresponds to
a non-linear equation. This specific case corresponds to a quadratic equation,
or a second-order polynomial, where the largest power of x is 2. A second-order
polynomial will have two roots. Note that both roots need not be real, so we
would say the equation could have up to two real roots. Likewise, when solving
systems of non-linear equations, we found that we could have more than one
unique set of roots.
For the case of a single variable, a linear equation takes the form ax + b = 0,
where a and b are constants. Any (single variable) equation that can not be put
in this form is non-linear. For the case of a linear equation, there exist just a
single root. Likewise, when we solve a system of linear equations, we will find that
there exists only a single set of unique roots.1 As an undergraduate when I took
my Circuit Analysis class, all I recall is setting up and solving systems of linear
equations. Solving systems of linear equations was also a major component of
your Mass and Energy Balance class. This is typically a major topic in a course in
Linear Algebra. And as you might imagine, methods specifically exist for solving
systems of linear equations. If you are interested, the “Open Textbook Library”
contains a number of excellent, freely available linear algebra textbooks.
8.10 Gaussian elimination
Let’s start by considering an example. Consider the following system of linear
equations:
2x + 3y = 2
(8.1)
x + 4z = 1
(8.2)
y +z =4
(8.3)
Let’s solve! First, we start by noticing that the system of equations is square. We
have 3 equations and 3 unknowns. So it can be solved. I might start by adding
−4×eq. 8.3 to eq. 8.2. This would result in:
x − 4y = −15
(8.4)
Next, add 43 eq. 8.4 to eq. 8.1. This results in:
2.75x = −9.25
(8.5)
1 This is true so long as the system of equations is square. That is, there is the same number of
equations as unknowns. This is not a mathematics class, so I will leave this at that.
8.10 Gaussian elimination
275
We can then solve this equation and get x = −3.3636. Now that we have x, we can
go to eq. 8.4 and eliminate x to solve for y = 2.9091. And then finally we could
go back to eq. 8.3 to eliminate y and find that z = 1.0909. Nice! We just solved a
system of of linear equations using an elimination strategy.
We could also represent the system of equations as a matrix and solve in
matrix form. We create a matrix by first re-writing the equations so that every
variable is present in each equation, and I will order the variables from left to
right as x, y, z.
2x + 3y + 0z = 2
(8.6)
x + 0y + 4z = 1
(8.7)
0x + y + z = 4
(8.8)
Where a variable was previously missing, I add zero times the variable. (Remember anything times zero is zero.) Next, we will construct a matrix containing the
coefficients and the right-hand-side of the equality. Row 1 will be eq. 8.6, row
2 will be eq. 8.7, and row 3 will be eq. 8.8. Column 1 will be the coefficient of x,
column 2 will be the coefficient of y, column 3 will be the coefficient of z, and
column 4 will be the right-hand-side of the equality.


2 3 0 2
1 0 4 1
0 1 1 4
Just as we previously multiplied equations by a constant and then added equations together, we would do the same thing here with our rows. The goal would be
for elements (1,1), (2,2), and (3,3) to be 1, while the other elements in rows 1–3 and
column 1–3 are zero. If we were then to translate back to equation form, we would
find that we just solved for x, y, and z. Solving in matrix form we would perform
row reductions, where the three elementary row operations are: 1) replacement,
2) interchange, and 3) scaling. The final form of the matrix just described would
correspond to reduced row echelon form. Algorithms exist to obtain the reduced
row echelon form of a matrix. MATLAB has a built-in function to do just this
called rref. Here is how we would use it to solve this system of equations:
>> A = [2, 3, 0, 2; 1, 0, 4, 1; 0, 1, 1, 4];
>> Sol = rref(A)
Sol =
1.0000
0
0
0
1.0000
0
0
0
1.0000
-3.3636
2.9091
1.0909
276
Chapter 8 Systems of Equations
A perfect match! If you wished to filter out the roots, you could do so in a number
of ways. We could create a vector:
>> S = Sol(:,4)
S =
-3.3636
2.9091
1.0909
This is a column vector. If you preferred a row vector, you could take the transpose.
This would give a result analogous if we had used fsolve. Know that you could
use fsolve to solve a system of linear equation. Use of rref is preferred, as
it avoids the need of initial conditions and you can be confident that it will be
successful.
8.11 What could go wrong?
The main issue that could cause rref to fail is if your system of equations is not
square. Typically, this happens when you have more unknowns than equations.
For this case, you can not solve for a unique set of solutions. The formal way to
check if your system of equations is square is to compute the rank of the matrix.
The rank of a matrix corresponds to the dimension of the column space of the matrix. For our purposes, the rank of the matrix tells us how many of the equations
are linearly independent. If the rank of the matrix is equivalent to the number of
variables, then the system of equations is square.
>> r = rank(A)
r = 3
How can we determine the number of unknowns (i.e., variables)? Simple:
>> c = length(A(1,:))-1
c = 3
or equivalently using the end keyword:
>> c = length(A(1,1:end-1))
c = 3
Here, A(1,:) corresponds to the first row of matrix A, all columns. So it pulls off
the first row as a row vector. We want the length of this vector minus 1; minus 1
because the last column corresponds to the right-hand-side of the equality in the
original equations. Equivalently, A(1,1:end-1) corresponds to the first row of
matrix A, and columns 1 to the second last (i.e., the last column minus 1). You can
8.12 Left division
277
check to make sure that the number of variables is equivalent to the rank. If not,
the system of equations is not square.
So what would be an example of a non-square system of equations? First, you
could have more equations than unknowns or more unknowns than equations.
The more common scenario is the system of equations appears square, but two or
more of the equations are linear combinations of each other. What if we slightly
changed our system of equations to the following:
2x + 3y = 2
(8.9)
2x + 4y + z = 6
(8.10)
y +z =4
(8.11)
t This corresponds to having
more unknowns than equations. Maybe a better way
to phrase is would be more
unknown than linearly independent equations.
Upon inspection, we find that the system of equations is not square. Eq. 8.10 is
equal to the sum of eqs. 8.9 and 8.11. Let’s see what happens if we attempt rref
and rank.
>> B = [2, 3, 0, 2; 2, 4, 1, 6; 0, 1, 1, 4];
>> Solb = rref(B)
Solb =
1.0000
0
-1.5000
-5.0000
0
1.0000
1.0000
4.0000
0
0
0
0
>> rb = rank(B)
rb = 2
Does the output make sense to you? A system of linear equations will have a
single, unique solution if the system of equations is square. This is why you spent
so much time learning how to set-up problems properly in your Mass and Energy
Balance course. As always, have a look at the documentation pages for rref and
rank to learn more.
8.12 Left division
Before wrapping-up, in addition to using rref, another technique to solve a
system of linear equations is left division. Let us again consider the system of
equations given by eqs. 8.1, 8.2, and 8.3. We can re-write the system in the general
form Ax = b, where A is a (square, n × n) matrix of the coefficients, b is a column
vector of the values on the right-hand-side of the equality, and x is a column
vector of the (unknown) variables we wish to solve for. For this problem this
would take the form:
t Let A and B correspond to
matrices. Then (right division) A/B = AB −1 . On the
other hand, (left division)
A\B = A −1 B
278
Chapter 8 Systems of Equations

   
2 3 0 x
2
1 0 4  y  = 1
0 1 1 z
4
We can solve as x = A −1 b, or equivalently using left division x = A\b. Let’s check
that this gives the same answer as rref:
>> A = [2,3,0; 1,0,4; 0,1,1];
>> b = [2;1;4];
>> Sol1 = inv(A)*b
Sol1 =
-3.3636
2.9091
1.0909
>> Sol2 = A^(-1)*b
Sol2 =
-3.3636
2.9091
1.0909
>> Sol3 = A\b
Sol3 =
-3.3636
2.9091
1.0909
We find that we get exactly the same solution as with rref using either notation,
where inv is the MATLAB function to compute the inverse of matrix, where
inv(A) and Aˆ(-1) are equivalent. You can use either solution strategy (and
either notation) and obtain the same result.
Know that in general I try to avoid left division. Why? Because too often I
confuse myself and make a mistake. Left division is not an operation I am used
to, and I suspect most of you have never heard of it before. What we commonly
refer to simply as division, could just as well be called right division and is designated with a forwardslash (/). Left division is designated with a backslash (\). To
understand the difference, let’s perform some unit testing in MATLAB.
>> 8/4
ans = 2
>> 8\4
ans = 0.5
8.13 Application: Raoult’s Law
279
We find for this simple scalar case that left division essentially reverses the division.
Again, this tends to confuse me, so I stick to using inv.
8.13 Application: Raoult’s Law
The ability to model phase-equilibria is of central importance in the design of
separation processes. Having discussed solving systems of equations, let’s consider the case of modeling binary vapor/liquid equilibrium using Raoult’s law.
Raoult’s law assumes:
1. The liquid phase is an ideal solution
2. The vapor phase is an ideal gas
3. We are well removed from the critical point such that f i0 ≈ p isat
If you do not know what this means, no worries, just be sure to sign-up for
Chemical Engineering Thermodynamics before you graduate. A situation where
Raoult’s law would be appropriate would be to model mixtures of alkanes at
ambient pressures.
A binary system at vapor/liquid equilibrium is governed by the following
system of non-linear equations:
y 1 p = x 1 p 1sat
(8.12)
y 2 p = x 2 p 2sat
(8.13)
where y 1 and y 2 are the vapor phase mole fractions of component 1 and 2, respectively, x 1 and x 2 are the liquid phase mole fractions of component 1 and 2,
respectively, where y 1 + y 2 = 1 and x 1 + x 2 = 1. The pressure is given by p, and p 1sat
and p 2sat are the pure component vapor pressure of component 1 and 2, respectively. The pure component vapor pressure is only a function of temperature, and
is commonly computed using Antoine’s equation:
log10 p sat = A −
B
T +C
(8.14)
If we add eqs. 8.12 and 8.13 together we obtain:
p = x 1 p 1sat + x 2 p 2sat
(8.15)
This is referred to as a “bubble p” calculation, because it corresponds to the
bubble point pressure for a given liquid composition. The expression additionally
gives the important result that the bubble line of as system obeying Raoult’s law
is a linear combination of the pure component saturation pressures.
A system for which Raoult’s law is appropriate is hexane/cyclohexane. For
this system, Antoine parameters and reference vapor/liquid equilibrium data is
280
Chapter 8 Systems of Equations
available for free from the Dortmund Data Bank (DDBST). We find the following
Antoine parameters, where the pressure is in mmHg and the temperature is in ◦ C
2
:
species
hexane
cyclohexane
cyclohexane
A
7.01051
6.85146
7.09926
B
1246.33
1206.47
1380.54
C
232.988
223.136
246.526
Tmin
–95
7
29
Tmax
235
81
280
For a binary system at two-phase equilibria, we have two degrees of freedom.
This means if we specify two intensive properties, any two intensive properties,
the thermodynamic state of our system is fixed. This could be T and p, T and x 1 ,
p and x 1 , or any other combination. This agrees with our system of equations;
if we specify two intensive properties, we are left with two equations with two
unknowns. We will consider the two very common cases of specifying T and x 1 ,
and specifying p and x 1 .
8.13.1 Specifying T and x 1
When we specify T and x 1 , we solve for p and y 1 . We can solve this problem a
number of ways using MATLAB. First, we could use fsolve. Having specified T
and x 1 , eqs. 8.12 and 8.13 (and using eq. 8.14) corresponds to a system of two
equations with two unknowns. We can therefore set-up an error function M-file
and solve using fsolve.
This equation is perhaps the most straightforward. We do not need to manipulate the provided equations at all, we can just go directly the MATLAB. The main
challenge that students often face is no matter how you decide to set-up your
code and error function, you will inevitably need to use an anonymous function.
2 http://ddbonline.ddbst.com/AntoineCalculation/AntoineCalculationCGI.exe
8.13 Application: Raoult’s Law
Listing 8.6 raoult_tx.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
% function res = raoult_tx(Ant,t_C,x1)
%
% Function to compute binary vapor liquid equilibrium using Raoult's law.
% Temperature and liquid mole fraction are known, and we solve for
% the vapor phase mole fraction and pressure.
%
% Inputs: Ant is a matrix containing the Antoine parameters for
%
component 1 and 2, respectively. The first row corresponds to
%
parameters for component 1, and the second row to parameters
%
for component 2, where there are three columns corresponding
%
to A, B, and C. The units are such that temperature is in C
%
and pressure is in mmHg.
%
t_C is the temperature of interest in degrees C
%
x1 is the liquid phase mole fraction
%
% Outputs: res is a vector of length 2, where the first element is
%
y1 and the second element is p. p will be in units of kPa
function res = raoult_tx(Ant,t_C,x1)
% Calculate the vapor pressure of component 1 and 2 in Pa
psat_1 = antoine(Ant(1,:),t_C);
psat_2 = antoine(Ant(2,:),t_C);
% Next, we need to set-up our error functions for use with fsolve.
% Remember fsolve expects an error function with a single input and
% single output variable. Here I have written a general error function
% for solving tx problems where the liquid mole fraction, x1, and the
% vapor pressure of each component are also taken as input. Remember we
% can pass this additional information using anonymous functions
error_func = @(Y) raoult_error_tx(x1,psat_1,psat_2,Y);
end
%
%
%
%
%
%
%
% Now we can use fsolve to solve for the vapor mole fraction and
% pressure. The order of the variables must agree with that used in the
% error function. We will also need to provide initial estimates. A
% safe estimate would be that y1 = 0.5, or somewhere in between 0 an 1,
% and for p, try x1*psat_1+x2*psat_2. You can play around with this.
y10 = 0.5;
p0 = x1*psat_1+(1-x1)*psat_2;
% Note that is the solution for p for the special case of Raoult's law,
% but not in general. So this will make a good initial guess for the
% general case.
res = fsolve(error_func,[y10,p0]);
%
% Converting the pressure to kPa before returning
res(2) = res(2)/1000;
function res = antoine(Ant,t_C)
Helper function to compute vapor pressure using Antoine's equation.
Temperature is in degrees C, and pressure is in mmHg
Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
t_C is the temperature in degrees C
281
282
Chapter 8 Systems of Equations
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
% Outputs: res is the vapor pressure in units of Pa.
%
function res = antoine(Ant,t_C)
% un-packing the vector of Antoine parameters
a = Ant(1);
b = Ant(2);
c = Ant(3);
% Calculating the vapor pressure. Temperature is in C and pressure in
% mmHg
p_mmHg = 10^( a-b/(t_C+c) );
% Converting to units of Pa (SI units) and returning
res = p_mmHg*(101.325e3/760);
end
% function res = raoult_error_tx(x1,psat_1,psat_2,Y)
%
% The error function for solving Raoult's law for binary vapor liquid
% equilibrium. Everything is in SI units.
%
function res = raoult_error_tx(x1,psat_1,psat_2,Y)
% un-packing Y
y1 = Y(1);
p = Y(2);
% Relating y1 and y2, and x1 and x2. This will make my code
% more readible
y2 = 1-y1;
x2 = 1-x1;
% Our error functions
res1 = y1*p - x1*psat_1;
res2 = y2*p - x2*psat_2;
% packing up our error functions
res = [res1,res2];
end
Let’s test our code for the binary system hexane(1)/cyclohexane(2) at 70 ◦ C (or
343.15 K) and x 1 = 0.125. At these conditions, experimental values from DDBST
are y 1 = 0.179 and p = 77.327 kPa. 3 To facilitate the calculation, I will create
another function specific to this system.
3 http://www.ddbst.com/en/EED/VLE/VLE%20Cyclohexane%3BHexane.php
8.13 Application: Raoult’s Law
Listing 8.7 hexane_cyclohexane_tx.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
%
%
%
%
%
%
function res = hexane_cyclohexane_tx(t_C, x1)
Function to compute p and y1 using Raoult's law for the binary system
hexane(1)/cyclohexane(2) at a given temperature in C (t_C) and liquid
phase mole fraction (x1)
function res = hexane_cyclohexane_tx(t_C, x1)
% Antoine parameters where T is in units C and p is in units of mmHg.
%
% Parameters for hexane(1) valid over the range -95 to 235 C
Ant_1 = [7.01051,1246.33,232.988];
% Parameters for cyclohexane(2) valid over the range 29 to 280 C
Ant_2 = [7.09926,1380.54,246.526];
%
% Packing the Antoine parameters to a matrix as used as an input to
% raoult_tx
Ant = [Ant_1; Ant_2];
% Performing Raoult's law calculation
res = raoult_tx(Ant,t_C,x1);
end
>> VLE = hexane_cyclohexane_tx(70,0.125)
VLE = 0.1709
76.9205
This is in excellent agreement with the reference data. Nice! Note that I could
have made raoult_tx a helper function within hexane_cyclohexane_tx, but I
did not do so here in the interest of space.
Alternatively, we could have solved the problem analytically. Remember that
p 1sat and p 2sat are only a function of T . So by specifying T , p 1sat and p 2sat are fixed
and can be computed directly using Antoine’s equation (eq. 8.15). Therefore,
we could first solve directly for p via eq. 8.15, and then solve for y 1 directly as:
y 1 = x 1 p 1sat /p.
283
284
Chapter 8 Systems of Equations
Listing 8.8 raoult_tx_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
% function res = raoult_tx_2(Ant,t_C,x1)
%
% Function to compute binary vapor liquid equilibrium using Raoult's law.
% Temperature and liquid mole fraction are known, and we solve for
% the vapor phase mole fraction and pressure. In this version we solve the
% system of equations analytically.
%
% Inputs: Ant is a matrix containing the Antoine parameters for
%
component 1 and 2, respectively. The first row corresponds to
%
parameters for component 1, and the second row to parameters
%
for component 2, where there are three columns corresponding
%
to A, B, and C. The units are such that temperature is in C
%
and pressure is in mmHg.
%
t_C is the temperature of interest in degrees C
%
x1 is the liquid phase mole fraction
%
% Outputs: res is a vector of length 2, where the first element is
%
y1 and the second element is p. p will be in units of kPa
function res = raoult_tx_2(Ant,t_C,x1)
% Calculate the vapor pressure of component 1 and 2 in Pa
psat_1 = antoine(Ant(1,:),t_C);
psat_2 = antoine(Ant(2,:),t_C);
% Next, perform a bubble p calculation to find p in Pa
%
p_Pa = x1*psat_1 + (1-x1)*psat_2;
% Then we can solve for y1
y1 = x1*psat_1/p_Pa;
% Last, convert the pressure to kPa, then pack up y1 and p and return
p_kPa = p_Pa/1000;
res = [y1,p_kPa];
end
% function res = antoine(Ant,t_C)
%
% Helper function to compute vapor pressure using Antoine's equation.
% Temperature is in degrees C, and pressure is in mmHg
%
% Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
%
t_C is the temperature in degrees C
% Outputs: res is the vapor pressure in units of Pa.
%
function res = antoine(Ant,t_C)
% un-packing the vector of Antoine parameters
a = Ant(1);
b = Ant(2);
c = Ant(3);
% Calculating the vapor pressure. Temperature is in C and pressure in
% mmHg
p_mmHg = 10^( a-b/(t_C+c) );
8.13 Application: Raoult’s Law
54
% Converting to units of Pa (SI units) and returning
55
res = p_mmHg*(101.325e3/760);
56 end
Listing 8.9 hexane_cyclohexane_tx_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
%
%
%
%
%
%
function res = hexane_cyclohexane_tx_2(t_C, x1)
Function to compute p and y1 using Raoult's law for the binary system
hexane(1)/cyclohexane(2) at a given temperature in C (t_C) and liquid
phase mole fraction (x1)
function res = hexane_cyclohexane_tx_2(t_C, x1)
% Antoine parameters where T is in units C and p is in units of mmHg.
%
% Parameters for hexane(1) valid over the range -95 to 235 C
Ant_1 = [7.01051,1246.33,232.988];
% Parameters for cyclohexane(2) valid over the range 29 to 280 C
Ant_2 = [7.09926,1380.54,246.526];
%
% Packing the Antoine parameters to a matrix as used as an input to
% raoult_tx
Ant = [Ant_1; Ant_2];
% Performing Raoult's law calculation
res = raoult_tx_2(Ant,t_C,x1);
end
>> VLE_2 = hexane_cyclohexane_tx_2(70,0.125)
VLE_2 = 0.1709
76.9205
Solving analytically we obtain the same result. So which solution is preferred? Of
course if you can solve a problem analytically it is preferred. However, if in the
process of working up your expressions to solve analytically there is opportunity
for you to make a mistake, then the numerical solution with fsolve may be
preferred. The use of fsolve is additionally nice as the solution for the case of
specifying p and x 1 is very similar. We will consider this case next
8.13.2 Specifying p and x 1
The other common scenario is p and x 1 are known, and we wish to solve for
T and y 1 . Let’s start by saving a copy of our function raoult_tx as raoult_px
and updating for this case. This case is a little more challenging. The reason
is when T is unknown, so are p 1sat and p 2sat . Let’s test our code for the binary
system hexane(1)/cyclohexane(2) at 101.33 kPa and x 1 = 0.3. At these conditions,
experimental values from DDBST are y 1 = 0.384 and T = 349.55 K (76.4 ◦ C).
285
286
Chapter 8 Systems of Equations
Listing 8.10 raoult_px.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
% function res = raoult_px(Ant,p_kPa,x1)
%
% Function to compute binary vapor liquid equilibrium using Raoult's law.
% Pressure and liquid mole fraction are known, and we solve for
% the vapor phase mole fraction and temperature.
%
% Inputs: Ant
is a matrix containing the Antoine parameters for
%
component 1 and 2, respectively. The first row corresponds to
%
parameters for component 1, and the second row to parameters
%
for component 2, where there are three columns corresponding
%
to A, B, and C. The units are such that temperature is in C
%
and pressure is in mmHg.
%
p_kPa is the pressure of interest in kPa
%
x1
is the liquid phase mole fraction
%
% Outputs: res is a vector of length 2, where the first element is
%
y1 and the second element is T. T will be in units of C
function res = raoult_px(Ant,p_kPa,x1)
% Next, we need to set-up our error functions for use with fsolve.
% Remember fsolve expects an error function with a single input and
% single output variable. Here I have written a general error function
% for solving px problems where the liquid mole fraction, x1, the pressure
% and the Antoine parameters are also taken as input.
% The Antoine parameters are essential because when we guess a value of
% temperature each iteration, we will need to compute the
% corresponding pure component vapor pressure. Remember we
% can pass this additional information using anonymous functions
error_func = @(Y) raoult_error_px(x1,p_kPa,Ant,Y);
end
%
%
%
%
%
%
%
% Now we can use fsolve to solve for the vapor mole fraction and
% temperatute. The order of the variables must agree with that used in the
% error function. We will also need to provide initial estimates. A
% safe estimate would be that y1 = 0.5, or somewhere in between 0 an 1,
% and for t, try 0.5*(tsat_1+tsat_2). You can play around with this.
%
% How do we get tsat_1 and tsat_2? It is the temperature where
% psat_1 = p and psat_2 = p.
%
y10 = 0.5;
tsat_1 = antoine_t(Ant(1,:),p_kPa);
tsat_2 = antoine_t(Ant(2,:),p_kPa);
t0 = 0.5*(tsat_1+tsat_2);
res = fsolve(error_func,[y10,t0]);
function res = antoine(Ant,t_C)
Helper function to compute vapor pressure using Antoine's equation.
Temperature is in degrees C, and pressure is in mmHg
Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
t_C is the temperature in degrees C
8.13 Application: Raoult’s Law
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
% Outputs: res is the vapor pressure in units of Pa.
%
function res = antoine(Ant,t_C)
% un-packing the vector of Antoine parameters
a = Ant(1);
b = Ant(2);
c = Ant(3);
% Calculating the vapor pressure. Temperature is in C and pressure in
% mmHg
p_mmHg = 10^( a-b/(t_C+c) );
% Converting to units of Pa (SI units) and returning
res = p_mmHg*(101.325e3/760);
end
% function res = antoine_t(Ant,p_kPa)
%
% Helper function to compute saturation temperature using Antoine's equation.
% Temperature is in degrees C, and pressure is in mmHg. The input
% pressure is in kPa, so we will need to convert.
%
% Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
%
p_kPa is the pressure in kPa
% Outputs: res is the temperature in C.
%
function res = antoine_t(Ant,p_kPa)
% un-packing the vector of Antoine parameters
a = Ant(1);
b = Ant(2);
c = Ant(3);
% Convert pressure to mmHg
p_mmHg = p_kPa*1000*(760/101.325e3);
% Calculating the vapor pressure. Temperature is in C and pressure in
% mmHg
res = -b/(log10(p_mmHg)-a) - c;
end
% function res = raoult_error_tx(x1,psat_1,psat_2,Y)
%
% The error function for solving Raoult's law for binary vapor liquid
% equilibrium. Everything is in SI units.
%
function res = raoult_error_px(x1,p_kPa,Ant,Y)
% un-packing Y
y1 = Y(1);
t_C = Y(2);
% with this T, calculate the vapor pressure
% Calculate the vapor pressure of component 1 and 2 in Pa
psat_1 = antoine(Ant(1,:),t_C);
psat_2 = antoine(Ant(2,:),t_C);
% Converting our pressure from kPa to Pa
p_Pa = p_kPa*1000;
% Relating y1 and y2, and x1 and x2. This will make my code
% more readible
y2 = 1-y1;
x2 = 1-x1;
% Our error functions
287
288
Chapter 8 Systems of Equations
109
110
111
112
113 end
res1 = y1*p_Pa - x1*psat_1;
res2 = y2*p_Pa - x2*psat_2;
% packing up our error functions
res = [res1,res2];
Listing 8.11 hexane_cyclohexane_px.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
%
%
%
%
%
%
function res = hexane_cyclohexane_px(p_kPa, x1)
Function to compute T and y1 using Raoult's law for the binary system
hexane(1)/cyclohexane(2) at a given pressure in kPa (p_kPa) and liquid
phase mole fraction (x1). The resulting temperature will be in C.
function res = hexane_cyclohexane_px(p_kPa, x1)
% Antoine parameters where T is in units C and p is in units of mmHg.
%
% Parameters for hexane(1) valid over the range -95 to 235 C
Ant_1 = [7.01051,1246.33,232.988];
% Parameters for cyclohexane(2) valid over the range 29 to 280 C
Ant_2 = [7.09926,1380.54,246.526];
%
% Packing the Antoine parameters to a matrix as used as an input to
% raoult_tx
Ant = [Ant_1; Ant_2];
% Performing Raoult's law calculation
res = raoult_px(Ant,p_kPa,x1);
end
>> VLE = hexane_cyclohexane_px(Ant,101.33,0.3)
VLE = 0.3814
76.6262
Once again this is in excellent agreement with the reference data.
Just like the last problem, we could alternatively solve the system of equations
sequentially. First, we perform a bubble p calculation (eq. 8.15). Eq. 8.15 again
has a single unknown. However, this time around p is known while T is unknown.
The complication is it is not clear if this can be solved analytically. However, a
single equation with a single unknown can readily be solved using fzero. Then
once we have solved for T , we can again directly solve for y 1 as y 1 = x 1 p 1sat /p.
Remember in our expressions, p 1sat and p 2sat are only functions of T .
8.13 Application: Raoult’s Law
Listing 8.12 raoult_px_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
% function res = raoult_px_2(Ant,p_kPa,x1)
%
% Function to compute binary vapor liquid equilibrium using Raoult's law.
% Pressure and liquid mole fraction are known, and we solve for
% the vapor phase mole fraction and temperature. In this version of the
% code we will solve the system of equations sequentially.
%
% Inputs: Ant
is a matrix containing the Antoine parameters for
%
component 1 and 2, respectively. The first row corresponds to
%
parameters for component 1, and the second row to parameters
%
for component 2, where there are three columns corresponding
%
to A, B, and C. The units are such that temperature is in C
%
and pressure is in mmHg.
%
p_kPa is the pressure of interest in kPa
%
x1
is the liquid phase mole fraction
%
% Outputs: res is a vector of length 2, where the first element is
%
y1 and the second element is T. T will be in units of C
function res = raoult_px_2(Ant,p_kPa,x1)
% Next, we need to set-up our error function for use with fzero.
% Remember fzero expects an error function with a single input and
% single output variable. Here I have written a general error function
% for solving px problems where the liquid mole fraction, x1, the pressure
% and the Antoine parameters are also taken as input.
% The Antoine parameters are essential because when we guess a value of
% temperature each iteration, we will need to compute the
% corresponding pure component vapor pressure. Remember we
% can pass this additional information using anonymous functions
error_func = @(t) raoult_error_px(x1,p_kPa,Ant,t);
% Now we can use fzero to solve for the temperature in C.
% We will also need to provide initial estimate of T or a search range.
% A tempting search range is between tsat_1 and tsat_2. However, I
% avoid this as it could fail if in the future, when this function is
% adapted for a non-ideal system, for an azeotropic system. I instead
% use an inital estimate of 0.5*(tsat_1+tsat_2). You can play around
% with this.
%
% How do we get tsat_1 and tsat_2? It is the temperature where
% psat_1 = p and psat_2 = p.
%
tsat_1 = antoine_t(Ant(1,:),p_kPa);
tsat_2 = antoine_t(Ant(2,:),p_kPa);
t0 = 0.5*(tsat_1+tsat_2);
t_C = fzero(error_func,t0);
% Now that we have t_C, solve for psat_1 in Pa
psat_1 = antoine(Ant(1,:),t_C);
% And now directly solve for y1. We will need to convert p from kPa to
% Pa
y1 = x1*psat_1/(p_kPa*1000);
289
290
Chapter 8 Systems of Equations
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
end
% Now packing up y1 and t_C to be returned
res = [y1,t_C];
% function res = antoine(Ant,t_C)
%
% Helper function to compute vapor pressure using Antoine's equation.
% Temperature is in degrees C, and pressure is in mmHg
%
% Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
%
t_C is the temperature in degrees C
% Outputs: res is the vapor pressure in units of Pa.
%
function res = antoine(Ant,t_C)
% un-packing the vector of Antoine parameters
a = Ant(1);
b = Ant(2);
c = Ant(3);
% Calculating the vapor pressure. Temperature is in C and pressure in
% mmHg
p_mmHg = 10^( a-b/(t_C+c) );
% Converting to units of Pa (SI units) and returning
res = p_mmHg*(101.325e3/760);
end
% function res = antoine_t(Ant,p_kPa)
%
% Helper function to compute saturation temperature using Antoine's equation.
% Temperature is in degrees C, and pressure is in mmHg. The input
% pressure is in kPa, so we will need to convert.
%
% Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
%
p_kPa is the pressure in kPa
% Outputs: res is the temperature in C.
%
function res = antoine_t(Ant,p_kPa)
% un-packing the vector of Antoine parameters
a = Ant(1);
b = Ant(2);
c = Ant(3);
% Convert pressure to mmHg
p_mmHg = p_kPa*1000*(760/101.325e3);
% Calculating the vapor pressure. Temperature is in C and pressure in
% mmHg
res = -b/(log10(p_mmHg)-a) - c;
end
% function res = raoult_error_tx(x1,psat_1,psat_2,t_C)
%
% The error function for solving for T via bubble p calculation using
% Raoult's law for binary vapor liquid equilibrium. Everything is in SI
% units, except T which is in C.
%
function res = raoult_error_px(x1,p_kPa,Ant,t_C)
% With this T, calculate the vapor pressure
8.13 Application: Raoult’s Law
109
110
111
112
113
114
115
116 end
% Calculate the vapor pressure of component 1 and 2 in Pa
psat_1 = antoine(Ant(1,:),t_C);
psat_2 = antoine(Ant(2,:),t_C);
% Converting our pressure from kPa to Pa
p_Pa = p_kPa*1000;
% Our bubble p error function
res = p_Pa - (x1*psat_1+(1-x1)*psat_2);
Listing 8.13 hexane_cyclohexane_px_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
%
%
%
%
%
%
function res = hexane_cyclohexane_px_2(p_kPa, x1)
Function to compute T and y1 using Raoult's law for the binary system
hexane(1)/cyclohexane(2) at a given pressure in kPa (p_kPa) and liquid
phase mole fraction (x1). The resulting temperature will be in C.
function res = hexane_cyclohexane_px_2(p_kPa, x1)
% Antoine parameters where T is in units C and p is in units of mmHg.
%
% Parameters for hexane(1) valid over the range -95 to 235 C
Ant_1 = [7.01051,1246.33,232.988];
% Parameters for cyclohexane(2) valid over the range 29 to 280 C
Ant_2 = [7.09926,1380.54,246.526];
%
% Packing the Antoine parameters to a matrix as used as an input to
% raoult_tx
Ant = [Ant_1; Ant_2];
% Performing Raoult's law calculation
res = raoult_px_2(Ant,p_kPa,x1);
end
>> VLE_2 = hexane_cyclohexane_px_2(Ant,101.33,0.3)
VLE_2 = 0.3814
76.6262
Again, with both methods we get the same answer. So which solution is preferred?
Again, this is a difficult question to answer. It is nice to solve sequentially and use
fzero. We know that so long as our error function is continuous and changes
signs, then it is guaranteed to find a solution. Both of these criteria will be satisfied
for this physical problem. But again, if working up the problem the allow you to
solve sequentially creates the opportunity for mistakes, then use of fsolve might
be preferred. I offer you both solution strategies to allow you to compare.
291
292
Chapter 8 Systems of Equations
8.14 Application: CSTR
8.14.1 CSTR Basics
t Non-steady-state behavior is something that you
need to worry about when
starting-up or shuttingdown a process, or when
there is a disturbance in
your system.
When I first took a course in Mass and Energy Balances, I never appreciated the
usefulness of being able to perform a mass balance. We’ll use one here to derive
the mole balance design equation for a continuous stirred tank reactor (CSTR).
We will assume we have an ideal CSTR operating at steady-state; that is, we will
assume the contents are perfectly mixed and their is no accumulation in our
system. For this problem we will further consider the case of an isothermal
reactor, so we can solve our mole balance independent of our energy balance. It
is common to look at cases when this is not the case, which requires solving your
mass and energy balance simultaneously. But that will be saved for your Kinetics
and Reactor Design course. In the spirit of promoting free texts, when I taught
Kinetics and Reactor Design I used the free text “A First Course on Kinetics and
Reaction Engineering,” by Carl Lund4 . The text was just being developed when I
was an undergraduate student taking the course with Professor Lund, and is the
original motivation for this text. I would encourage you to have a look at the text
and promote its use. I will also point out the text provides many good MATLAB
resources for solving problems in that course.
Below is an image of a CSTR which I took from Wikipedia.
4 http://wwwresearch.sens.buffalo.edu/karetext/title/title.shtml
8.14 Application: CSTR
293
Figure 8.2 Cartoon of a CSTR.
In a CSTR, reagents are continuously fed into the reactor at a known flow rate
and with known temperature and composition. In addition, a product stream
is continuously removed from the reactor. If the total mass flowing into the
reactor equals the total mass flowing out of the reactor, and if each of the flow
rates, compositions, and temperatures does not change with time, the reactor
is said to be operating at steady-state. At steady-state, there is no accumulation
in our system, and for the isothermal case, the temperature of the inlet equals
the temperature of the outlet. For this problem, we will add (if the reaction is
endothermic) or remove (if the reaction is exothermic) heat so that the exit inlet
and outlet temperature are the same.
In our ideal case, we said that the reactor was perfectly mixed. The composition and temperature of the product stream is therefore identical to the contents
of the reactor, which correspond to the conditions at which the reaction takes
294
Chapter 8 Systems of Equations
place.
Let’s write a mole balance for our system for component A:
INPUT + GENERATION = OUTPUT + ACCUMULATION
We are assuming steady-state operation, so there is no accumulation. Our expression therefore reduces to:
INPUT + GENERATION = OUTPUT
t This is a homogeneous,
bulk phase reaction. The
rate of reaction is therefore
made intensive by dividing
by the volume of the system.
If we double the size of the
system we expect that rate
of generation/consumption to double, so volume
is the natural scale factor.
However, there are other
conventions. A heterogeneous, catalytic reaction
takes place at the surface
of a solid catalyst. (It is heterogeneous because the
catalyst is solid and in the
presence of either a liquid
or gas.) For this case, the
rate of reaction is made intensive by dividing by the
surface area of the catalyst.
This is again the natural
scale factor because if we
double the surface area
of the catalyst, we expect
the rate of generation/consumption to double.
Consider an arbitrary species A involved in a chemical reaction. We will assume that the only way a mole of A can be generated/consumed is via a chemical
reaction. If we consider here just a single chemical reaction taking place:
GENERATION = ν A V r
where ν A is the stoichiometic coefficient of A, ν A r is the per unit volume rate of
generation/consumption of A, and V is the volume of the system. With this our
mole balance design equation becomes:
INPUT + ν A V r = OUTPUT
Last, we designate our inlet molar flow rate of component A as ṅ 0A and the outlet
molar flow rate of component A as ṅ A . This leads to the final expression:
ṅ 0A + ν A V r = ṅ A
Or more commonly:
ν A V r = ṅ A − ṅ 0A
A common measure of the reaction progress is the fractional conversion. The
fractional conversion of A may be computed as
fA =
ṅ 0A − ṅ A
ṅ 0A
It tells us what fraction of A in the inlet has been consumed.
8.14.2 A single reaction example
Let’s consider an example problem involving a single reaction.
The conversion of A to B (A → B ) takes place in an aqueous solution in an
isothermal CSTR. The inlet to the reactor is a 2 M solution of A at 300 K at a total
flow rate of 1200 L/hour. (Recall 1 M = 1 mol/L.) Calculate the volume of the
reactor necessary to reach 0.8 fractional conversion of A. The reaction is first
order in A and the rate coefficient obeys the Arrhenius expression with a preexponential term equal to 2.4 × 108 s−1 and an activation energy of 15.3 kcal/mol.
Mathematically this means
r = kC A
8.14 Application: CSTR
295
k = k 0 exp[−E / (RT )]
k 0 = 2.4 × 108 s−1
E = 15.3kcal/mol
Okay, let’s start with some simplifying assumptions. We have a reaction that
takes place in the aqueous phase. The initial concentration of A is rather dilute,
2 moles per liter of water. There are approximately 55.35 moles of water per
liter, so this corresponds to a mole fraction of just 0.035. And since it is a 1 to 1
reaction (for every mole of B created a mole of A is consumed), the total molar
concentration of A and B will remain the same (a small value). Therefore, it is
very reasonable to assume that the inlet and outlet volumetric flow rate (V̇ ) are
constant and equal to 1200 L/hour. This is a very common approximation.
This allows us to do a number of things. First, we can write our inlet and outlet
molar flow rates in terms of molar concentrations and volumetric flow rates:
ṅ 0A = C A0 V̇
ṅ A = C A V̇
Additionally, we can re-write the fractional conversion using molar concentrations:
C 0 −C A
fA = A 0
CA
In the present problem I asked you to size the reactor. That is, for a given flow
rate, find the total reactor volume needed. I just as well could have specified the
total volume and asked you to find the inlet flow volume. The problem can be
generalized by defining the space time:
τ=
V
V̇
The space time has units of time, and can be thought of as the average time a
fluid element remains inside the reactor. Solving with respect to space time is
also useful because it will facilitate comparison to a batch reactor or plug flow
reactor (PFR).
Let us also pause and take note of the units. I have provided you with the
general rate expression. If you want a rate expression for the consumption of A
or generation of B, you multiply by the appropriate stoichiometric coefficient.
Remember stoichiometric coefficients for reactants are negative (they are being
consumed) and positive for reactants. So for the rate of consumption of A
r A = ν A r = −kC A
Likewise, the rate of generation of B would be
r B = νB r = kC A
t Also note here that having
a dilute solution is a main
reason we can assume the
system is isothermal. There
is an excess amount of water present that serves as a
heat bath
296
Chapter 8 Systems of Equations
Get the idea? The units of the rate will be moles per volume per time. We will need
to make sure we are using the same units for moles, volume, and time throughout
the problem. The rate constant (k) has the same units as the pre-exponential
term (k 0 ) of 1/s. However, the flow rate given in the problem is in L/h.
We will need to make sure our units are consistent when you solve. The term
in the exponential, E /(RT ) is dimensionless. We are given E in kcal/mol. So
unless we convert it, we should use R in units of kcal/(mol K) and T in units of K.
We could alternatively convert E to say J/mol and use the only value of R that I
remember, R = 8.314 J/(mol K).
Additionally, in this problem we have set everything up to solve using A. As a
future note, know that you do not have to use A if you do not want to. Likewise,
we do not need to solve a mole balance for B in order to determine the molar flow
rate of B. The moles of A and B are related by stoichiometry. For every mole of A
that is consumed, a mole of B is created in our reaction. Therefore,
ṅ 0A − ṅ A = ṅ B − ṅ B0
and since we started with no moles of B
ṅ 0A − ṅ A = ṅ B
C A0 −C A = C B
My very last note is on the rate expression itself. Here we have a relatively
simple expression. This is generally not the case. Depending on the proposed
mechanism or if we have a reversible reaction, the expression will likely be much
more complicated. (No worries, MATLAB does not care!) But the point I would
like to make is that you can’t (and never should be expected to) just look at a
reaction and know what the form of the rate of reaction should be. The only way
is to propose a model (in various ways) and test it by fitting to experimentally
generated data.
Example 8.3
The conversion of A to B (A → B ) takes place in an aqueous solution in an isothermal CSTR. The inlet to the reactor is a 2 M solution of A at 300 K at a total flow
rate of 1200 L/hour. (Recall 1 M = 1 mol/L.) Calculate the volume of the reactor
necessary to reach 0.8 fractional conversion of A. The reaction is first order in A and
the rate coefficient obeys the Arrhenius expression with a pre-exponential term
equal to 2.4 × 108 s−1 and an activation energy of 15.3 kcal/mol. Mathematically
this means
r = kC A
k = k 0 exp[−E / (RT )]
k 0 = 2.4 × 108 s−1
E = 15.3kcal/mol
8.14 Application: CSTR
297
Solution: We begin with an isothermal CSTR in which we have as single reaction
occurring, A → B . For this system our mole balance design equation is:
ν A V r = ṅ A − ṅ 0A
We are told that A and B are dilute such that:
ṅ A = V̇ C A
ṅ 0A = V̇ C A0
where V̇ is the volumetric flow rate which can be assumed constant. Plugging into
our mole balance design equation we have:
¡
¢
ν A V r = V̇ C A −C A0
(8.16)
Additionally, we are told that the rate is first order in C A
r = kC A
where we are provided with the parameters to compute k. In the problem statement we are additionally provided with values of C A0 and V̇ . Plugging the rate
expression into eq. (8.16), we find we have a single equation with two unknowns:
C A and V . To solve, we need another equation. We are provided with the constraint
that the fractional conversion is 0.8:
fA =
ṅ 0A − ṅ A
ṅ 0A
=
C A0 −C A
C A0
(8.17)
Equation (8.16) and 8.17 corresponds to a system of two equation with two unknowns which can be solved. Note that eq. (8.17) could be solved first to find C A ,
and then used in eq. (8.16) to computed V . This would involve an fzero call for
each case or you could solve analytically. Alternatively, we can just use fsolve to
solve the system of equations. Both work and will yield the same result. I will use
the latter approach.
Before solving, a few comments on about units. The unit of volume used by V ,
V̇ , C A and C A0 will be liter (L). The parameters provided in the problem statement
use a unit of time of seconds for k and hours for V̇ . A unit conversion will be
necessary to make sure both use the same unit of time. Last, the term E / (RT ) is
dimensionless and T must be in absolute units. I will convert E to J/mol and use
Kelvin for T . Let’s do it!
298
Chapter 8 Systems of Equations
Listing 8.14 cstr_single_v.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
% Function to solve for the volume of a CSTR to acheive a fractional
% conversion of 0.8. I will hardcode everything for the specific problem
% at hannd. You need just call the function to solve.
%
function res = cstr_single_v()
% First we will list the provided parameters
% Pre-exponential term, 1/s
k0 = 2.4e8;
% Activation energy, kcal/mol
ea_kcal = 15.3;
% Converting the activation energy to J/mol
ea_j = ea_kcal*1000*4.184;
% Molar gas constant in J/(mol K)
rg = 8.314;
% Temperature in K
t_K = 300;
% volumetric flow rate, L/hour
v_Lh = 1200;
% Converting the volumetric flow rate to L/s
v_Ls = v_Lh/3600;
% Initial concentration of A, mol/L
cA0 = 2;
% Fractional conversion
fA = 0.8;
% Stoichiometric coefficient of A
nuA = -1;
% Compute the rate constant
k = k0*exp(-ea_j/(rg*t_K));
% We need to create an anonymous function to pass to our error
% function: v_Ls, cA0, fA, nuA, k
cstr_error_func = @(CV) cstr_mole_balance(CV,v_Ls,cA0,fA,nuA,k);
% Using fsolve to solve for cA and V. I will use an initial estimate of
% cA = 1 M and V = 10 L. If fsolve fails we can try and tweak these.
res = fsolve(cstr_error_func,[1,10]);
end
% Error function corresponding to our mole balance design equation
% and fractional conversion. The first argument will be a vector, CV, where
% the first element is cA and the second element is v, the two variables we
% wish to solve for using fsolve. The others are parameters that will be
% passed using an anonymous function.
function res = cstr_mole_balance(CV,v_Ls,cA0,fA,nuA,k)
% Un-packing
cA = CV(1);
v = CV(2);
% The rate expressions
8.14 Application: CSTR
54
55
56
57
58
59
60
61
62
63
64 end
299
r = k*cA;
% Now computing the error functions.
% The mole balance re-written as an error function
res1 = nuA*v*r-v_Ls*(cA-cA0);
res2 = fA - (cA0-cA)/cA0;
% Packing up the rate functions
res = [res1,res2];
>> sol = cstr_single_v
sol = 0.4000 778.3349
We find we need a volume of 778.3349 L. Note that I tried other initial estimates of
V , but the final value seemed relatively insensitive to the initial guess.
Note, I created a function with no inputs to solve the problem. You will find that
I do this a lot. You need just run the function to obtain the solution. However,
note that beginning with MATLAB R2016b, you can now add functions to scripts to
achieve the same effect.
Example 8.4
Next, generalize your solution to the previous example. Assume that the inlet
volumetric flow rate was not specified. (But do assume it is large enough to assume
the volumetric flow rate is constant.) Solve for and create a plot of the fractional
conversion of A as a function of the space time. Solve for the space time required
to achieve a fractional conversion of 0.8. In general, as space time increases
does fractional conversion increase or decrease? So if one wished to increase the
fractional conversion of a CSTR, what should they do to the inlet volumetric flow
rate (or the reactor volume if possible)?
Solution: Next, we generalize eq. (8.16) to use space time, where τ = V /V̇ . This
gives:
ν A τr = C A −C A0
(8.18)
We have two tasks. First, knowing ν A , k, and C A0 , solve for the required τ to achieve
a fractional conversion of 0.8. The solution is virtually the same as the previous
example. Second, loop over values of τ, and for each value of τ solve for and plot
f A . To accomplish this, with a specified value of τ, we can solve eq. (8.18) for C A ,
which can then be used to compute the fractional conversion. For this case, we will
have a single equation with a single unknown, so the use of fzero will be preferred
over fsolve. Note that we could alternatively solve the expression analytically
for C A for this simple case. But it is easy enough to solve numerically, and the
solution would be the same no matter how complicated the rate expression ever
300
Chapter 8 Systems of Equations
became. Following our previous note on units, τ and k will use the same units of
time. I will use seconds to solve. However, the resulting time to achieve a fractional
conversion of 0.8 is very large in seconds, so I will convert the output to minutes to
make the output more readable.
Listing 8.15 cstr_single_tau.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
% Function to solve for the space time of a CSTR to acheive a fractional
% conversion of 0.8. Also, create a plot of the fractional version versus
% space time. I will hardcode everything for the specific problem
% at hannd. You need just call the function to solve.
%
function res = cstr_single_tau()
% First we will list the provided parameters
% Pre-exponential term, 1/s
k0 = 2.4e8;
% Activation energy, kcal/mol
ea_kcal = 15.3;
% Converting the activation energy to J/mol
ea_j = ea_kcal*1000*4.184;
% Molar gas constant in J/(mol K)
rg = 8.314;
% Temperature in K
t_K = 300;
% Initial concentration of A, mol/L
cA0 = 2;
% Stoichiometric coefficient of A
nuA = -1;
% Compute the rate constant
k = k0*exp(-ea_j/(rg*t_K));
% Using fsolve to solve for cA and tau to acheive a fractional
% conversion of 0.8. We will using an anonymous function to pass cA0,
% nuA, and k to the error function.
cstr_error_func_08 = @(CT) cstr_mole_balance_08(CT,cA0,nuA,k);
res = fsolve(cstr_error_func_08,[1,10]);
% Converting the computed space time to minutes.
res(2) = res(2)/60;
% Next, creating the desired plot of fractional conversion versus space
% time.
% Space time in minutes
Tau = linspace(0,240);
% Vector to store computed concentrations of A
CA = zeros(1,length(Tau));
for i = 1:length(Tau)
% Each iteration define an anonymous function to pass Tau(i), k,
% nuA, and cA0 to the error function.
cstr_error_func = @(cA) cstr_mole_balance(cA,Tau(i),k,nuA,cA0);
% For fzero, I will set bounds from 0 to cA0.
8.14 Application: CSTR
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
CA(i) = fzero(cstr_error_func,[0,cA0]);
end
% Computing the fractional conversion
FA = (cA0-CA)./cA0;
% Plotting
hold on
plot(Tau,FA,'-k')
% Just for fun, plot our point corresponding to a fractional conversion
% of 0.8.
plot(res(2),0.8,'ko')
xlabel('\tau [min]')
ylabel('f_{A}')
print('-depsc','cstr_single_tau.eps')
end
% Error function corresponding to our mole balance design equation
% and fractional conversion. The first input variable is a vector of length
% 2 containing cA and tau which we will solve for using fsolve. The
% remaining variables are parameters that will be passed via an anonymous
% function.
%
function res = cstr_mole_balance_08(CT,cA0,nuA,k)
% Un-packing
cA = CT(1);
tau = CT(2);
% The desired fractional conversion
fA = 0.8;
% The rate expressions
r = k*cA;
% Now computing the error functions.
% The mole balance re-written as an error function
res1 = nuA*tau*r-(cA-cA0);
res2 = fA - (cA0-cA)/cA0;
end
% Packing up the rate functions
res = [res1,res2];
% Error function corresponding to our mole balance design equation.
% In this version, tau will be specified and we will just solve for
% cA. The first input variable is cA which we will solve for using fzero,
% and the remaining variables are parameters that will be passed via an
% anonymous function.
%
function res = cstr_mole_balance(cA,tau,k,nuA,cA0)
% Converting the space time from minutes to seconds
t = tau*60;
301
302
Chapter 8 Systems of Equations
103
104
105
106
107
108
109 end
% The rate expressions
r = k*cA;
% Now computing the error functions.
% The mole balance re-written as an error function
res = nuA*t*r-(cA-cA0);
>> sol = cstr_single_tau
sol = 0.4000 38.9167
1
0.9
0.8
0.7
fA
0.6
0.5
0.4
0.3
0.2
0.1
0
0
50
100
150
200
250
[min]
Figure 8.3 cstr_single_tau
We find that as space time increases, fractional conversion increases. The rate of
increase is greatest initially and then slows. If one wished to increase the fractional
conversion of a CSTR, they can decrease the volumetric flow rate, keeping the
volume of reactor constant, to increase the space time.
8.15 Glossary
8.15 Glossary
row vector: In MATLAB, a matrix that has only one row.
column vector: A matrix that has only one column.
transpose: An operation that transforms the rows of a matrix into columns (or
the other way around, if you prefer).
system of equations: A set of equations written in terms of a set of variables such
that the equations are inter-tangled.
paragraph: A chunk of code that makes up part of a function, usually with an
explanatory comment.
unpack: To copy the elements of a vector into a set of variables.
pack: To copy values from a set of variables into a vector.
rank: An operation that returns the number of linearly independent equations.
303
304
Chapter 8 Systems of Equations
8.16 Exercises
Exercise 8.1 A mixture contains n-pentane (1), n-hexane (2), and n-heptane (3) at equal
mole fractions (z 1 = z 2 = z 3 ), where the term in parentheses corresponds to the component index we will use. The temperature is 55 ◦ C. The pressure is adjusted so that 75%
of the mixture is vapor (V /F = 0.75) while the temperature remains constant at 55 ◦ C.
Determine the pressure and compositions of the two phases.
At 55 ◦ C the vapor pressure of n-pentane, n-hexane, and n-heptane are P 1sat = 1.903
bar, P 2sat = 0.644 bar, and P 3sat = 0.231 bar, respectively.
Figure 8.4 Illustration of our system.
To solve, we will use the illustration of the flash drum above to set-up our system of
equations to solve. A few notes first. F , L, and V correspond to the inlet flow rate, and
outlet liquid and vapor flow rates, respectively. We will solve using the fraction vaporized
(V /F ) and the fraction remaining liquid (L/F ). The inlet, liquid- and vapor-phase mole
fractions of component i are given by z i , x i , and y i , respectively. Finally, note that
specifying the temperature is equivalent to specifying P isat .
With that, we are ready to set-up our system of equations. We will end up with a total
of 8 equations with 8 unknowns. For our 3 component system, we can write 3 iso-fugacity
(equilibrium) expressions, 3 mass balances (or equivalently mole balances since we have
no reactions), and two constraints that our liquid and vapor mole fractions sum to 1.
Since we have a system of linear alkanes, we can assume Raoult’s law is appropriate for
our iso-fugacity expressions. This leads to the following system of equations:
y 1 P = x 1 P 1sat
y 2 P = x 2 P 2sat
y 3 P = x 3 P 3sat
z1 F = y 1V + x1 L
z2 F = y 2V + x2 L
z3 F = y 3V + x3 L
1 = y1 + y2 + y3
1 = x1 + x2 + x3
8.16 Exercises
305
Then to get our final expression, we divide through our mole balances by F , giving:
y 1 P = x 1 P 1sat
y 2 P = x 2 P 2sat
y 3 P = x 3 P 3sat
z 1 = y 1 V /F + x 1 L/F
z 2 = y 2 V /F + x 2 L/F
z 3 = y 3 V /F + x 3 L/F
1 = y1 + y2 + y3
1 = x1 + x2 + x3
This results in a system of 8 equation with 8 unknowns which we can solve. Our 8
unknowns are: y 1 , y 2 , y 3 , x 1 , x 2 , x 3 , L/F and P . When you solve, note that all of the
unknowns except P that we will solve for will be between 0 and 1. Also, using our values of
P isat in bar, our computed P will also be in bar. Solving, I obtain: y 1 = 0.4055, y 2 = 0.3463,
y 3 = 0.2482, x 1 = 0.1167, x 2 = 0.2946, x 3 = 0.5887, L/F = 0.25, and P = 0.5479 bar.
Now just think, with this code you can become a design engineer.
Exercise 8.2 Reactants A and B can react irreversibly to produce either a desired product,
D, or an undesired product, U, as shown in the equations below:
A +B → D
(1)
A +B →U
(2)
The corresponding rate expressions are given by:
¾
½
¡
¢
−15300J/mol
CA
r 1 = 1.12 × 102 min−1 exp
RT
½
¾
¡
¢
−23700J/mol
r 2 = 1.87 × 102 min−1 exp
CB
RT
A liquid feed mixture containing 10 mol A per gallon and 12 mol B per gallon at 350 K
is fed to a 25 gallon isothermal CSTR (operating at 350 K) at a rate of 12.5 gallons per
minute.
What is the conversion of the limiting reagent? What is the outlet selectivity (in mol
D per mol U)? The limiting reagent will be the reactant that will be consumed first. Here,
for each mol of A consumed a mol of B is consumed. The limiting reagent will therefore
be the species with the smallest concentration in the feed. Why would the conversion
with respect the limiting reagent be preferred?
For this set of reactions, the following general system of mole balance design equations will apply:
¡
¢
τ ν A,1 r 1 + ν A,2 r 2 = C A −C A0
¡
¢
τ νB,1 r 1 + νB,2 r 2 = C B −C B0
¡
¢
0
τ νD,1 r 1 + νD,2 r 2 = C D −C D
306
Chapter 8 Systems of Equations
¡
¢
0
τ νU ,1 r 1 + νU ,2 r 2 = CU −CU
Exercise 8.3 Please solve the following system of 10 equations with 10 unknowns:
7x 1 + 4x 2 + 3x 3 + 8x 4 + 8x 5 + 4x 6 + 1x 7 + 2x 8 + 5x 9 + 1x 10 =4
4x 2 + 7x 3 + 3x 4 + 3x 5 + 8x 6 + 1x 7 + 8x 8 + 1x 9 + 10x 10 =9
3x 1 + 8x 2 + 7x 3 + 5x 4 + 8x 5 + 6x 6 + 5x 7 + 3x 8 + 2x 9 =2
8x 2 + 2x 3 + 7x 4 + 2x 5 + 5x 6 + 8x 7 + 5x 8 + 9x 9 + 8x 10 =3
1x 1 + 2x 2 + 1x 3 + 9x 4 + 9x 5 + 9x 6 + 9x 7 + 2x 8 + 2x 9 + 8x 10 =1
8x 1 + 5x 2 + 5x 3 + 10x 4 + 3x 5 + 3x 6 + 1x 7 + 6x 8 + 8x 9 + 9x 10 =1
7x 1 + 4x 2 + 10x 3 + 5x 4 + 2x 5 + 8x 6 + 6x 7 + 3x 8 + 5x 9 + 1x 10 =9
3x 1 + 6x 2 + 3x 3 + 1x 4 + 3x 5 + 8x 6 + 5x 7 + 7x 8 + 10x 9 + 4x 10 =6
10x 1 + 7x 2 + 6x 3 + 1x 4 + 6x 5 + 4x 6 + 7x 8 + 1x 9 + 3x 10 =5
8x 2 + 2x 3 + 3x 4 + 5x 5 + 6x 6 + 3x 7 + 7x 8 + 4x 9 + 8x 10 =1
Solving, I obtain the following solution:
x 1 = −0.8120
x 2 = −2.3182
x 3 = −8.7617
x 4 = 13.8025
x 5 = −9.2603
x 6 = 4.7159
x 7 = 2.1875
x 8 = 22.3778
x 9 = −8.4912
x 10 = −14.4467
Exercise 8.4 We have seen how we can use fsolve to model binary vapor/liquid equilibrium. In Listing 8.6 we specified temperature and the liquid-phase mole fraction, and
solved for the pressure and vapor-phase mole fraction. This was applied to the binary
system of hexane(1)/cyclohexane(2). Here, let’s go a step further and construct a px y
phase diagram. What do we mean by this? Well, temperature is known and constant.
x 1 spans the range 0 to 1. So loop over values of x 1 over the range 0 to 1, and solve for
the corresponding y 1 and pressure. Then plot p vs x 1 and p vs y 1 on the same plot. You
should connect the points with a line to create a phase-envelope. The more points you
use, the smoother your curve.
In the table below, I provide reference data from DDBST at 343.15 K. Perform your
calculations at this temperature. Then on the same plot, add the reference data as symbols. How do the results compare? To your plot, please add a title with the system name
(hexane(1)/cyclohexane(2)) and temperature, axis labels, and a legend to distinguish
between your Raoult’s law predictions and the reference data.
8.16 Exercises
p/kPa
77.327
81.607
85.673
90.033
94.019
97.965
100.818
307
x1
0.12500
0.25000
0.37500
0.50000
0.62500
0.74200
0.87500
y1
0.17900
0.33600
0.46500
0.59000
0.70800
0.80600
0.90100
Here I have copied our table of Antoine parameters, where T is in ◦ C and p is in
mmHg.
species
hexane
cyclohexane
cyclohexane
A
7.01051
6.85146
7.09926
B
1246.33
1206.47
1380.54
C
232.988
223.136
246.526
Tmin
–95
7
29
Tmax
235
81
280
Exercise 8.5 Continuing from the previous exercise, in Listing 8.10 we specified pressure
and the liquid-phase mole fraction, and solved for the temperature and vapor-phase
mole fraction. This was applied to the binary system of hexane(1)/cyclohexane(2). Here,
let’s go a step further and construct a T x y phase diagram. What do we mean by this?
Well, pressure is known and constant. x 1 spans the range 0 to 1. So loop over values of x 1
over the range 0 to 1, and solve for the corresponding y 1 and temperature. Then plot T vs
x 1 and T vs y 1 on the same plot. You should connect the points with a line to create a
phase-envelope. The more points you use, the smoother your curve.
In the table below, I provide reference data from DDBST at 101.33 kPa. Perform your
calculations at this pressure. Then on the same plot, add the reference data as symbols.
How do the results compare? To your plot, please add a title with the system name (hexane(1)/cyclohexane(2)) and pressure, axis labels, and a legend to distinguish between
your Raoult’s law predictions and the reference data. A table of Antoine parameters is
provided in the previous exercise.
T /K
353.87
352.65
352.15
351.35
350.50
349.90
349.55
348.85
348.55
347.85
346.85
346.05
345.20
344.55
344.00
343.55
343.20
341.85
x1
0.00000
0.08200
0.12100
0.16700
0.22600
0.27000
0.30000
0.35300
0.38400
0.43800
0.52300
0.59100
0.67100
0.74000
0.79700
0.84000
0.87700
1.00000
y1
0.00000
0.12100
0.16700
0.22600
0.30000
0.35300
0.38400
0.43800
0.46800
0.52300
0.60200
0.67100
0.74000
0.79200
0.84000
0.87700
0.90400
1.00000
308
Chapter 8 Systems of Equations
T x y phase-diagrams are very important for the design of distillation columns. Columns
are typically operated at a constant pressure (typically near atmospheric), and a temperature gradient is present from the top to the bottom of the column. It can be extremely
useful to visualize such processes using what is called a McCabe-Thiele diagram. In a
McCabe-Thiele diagram, we take all of our data from our T x y, and plot y vs x. Connect the points to create a loci of equilibrium compositions. Then last, as a reference,
construct a y = x line from x = y = 0 to x = y = 1. Give it a try and label your plot.
Exercise 8.6 In Exercises 8.4 and 8.5 we constructed px y and T x y phase diagrams for a
system obeying Raoult’s law. Raoult’s law makes three important assumptions:
1. The liquid phase is an ideal solution
2. The vapor phase is an ideal gas
3. We are well removed from the critical point such that f i0 ≈ p isat
Again, if you do not know what this means, no worries, just be sure to sign-up for Chemical
Engineering Thermodynamics before you graduate. The second and third assumptions
are generally very good so long as we are at low pressures. The first assumption is generally
where Raoult’s law breaks down. We account for solution phase non-ideality using activity
coefficients (Lewis/Randall or Raoult’s law normalized), which leads to the “modified”
Raoult’s law expressions. A binary system at vapor/liquid equilibrium is governed by the
following system of non-linear equations:
y 1 p = γ1 x 1 p 1sat
y 2 p = γ2 x 2 p 2sat
where γ1 and γ2 are the activity coefficient of component 1 and 2, respectively. The pure
component vapor pressure is only a function of temperature, and is commonly computed
using Antoine’s equation:
log10 p sat = A −
B
T +C
The activity coefficient is, in theory, a function of temperature, pressure, and composition.
However, for condensed phase systems (liquids) the pressure dependence can typically be
safely ignored. For this problem, this means that γ1 and γ2 are functions only temperature
and composition.
As used in Exercise 7.4, one way to model the activity coefficient is using the NRTL
(non-random two-liquid) equation:
·
µ
ln γ1 = x 22 τ21
G 21
x 1 + x 2G 21
¶2
·
µ
τ12
G 12
x 2 + x 1G 12
¶2
ln γ2 = x 12
+
+
τ12G 12
(x 2 + x 1G 12 )2
τ21G 21
(x 1 + x 2G 21 )2
where
τ12 =
and
¸
g 12 − g 22
g 21 − g 11
τ21 =
RT
RT
¸
8.16 Exercises
309
G 12 = exp (−α12 τ12 ) G 21 = exp (−α21 τ21 )
where R is the molar gas constant. Note that the terms τ12 and τ21 are dimensionless.
In theory, the parameter g i j is an energy parameter characteristic of the i - j molecular
interaction, and αi j is the non-randomness parameter corresponding to the inverse of
the coordination number.
For this problem, we will build-upon our code from Exercises 8.4 and 8.5 to construct
px y and T x y phased diagrams for a binary system using the modified Raoult’s law
expressions. We will apply this to the binary system of ethyl acetate(1)/ethanol(2) studied
in Exercise 7.4. For this system The vapor pressure of ethyl acetate (p 1sat ) and ethanol
(p 2sat ) can be computed using Antoine’s equation with parameters provided in the table
below from DDBST. With these parameters, the vapor pressure in units of mmHg, and T
is the temperature in ◦ C.
compound
ethyl acetate
ethanol
A
7.10179
8.20417
B
1244.95
1642.89
C
217.9
230.3
Tmin [◦ C]
16
–57
Tmax [◦ C]
76
80
As in Exercise 7.4 we will use the NRTL equation to model the activity coefficients
with the following set of parameters:
α12 = α21 = 0.296
g 12 − g 22 = 72.347 J/mol g 21 − g 11 = 3682.7378 J/mol
1. Construct a px y phase diagram for the binary system ethyl acetate(1)/ethanol(2)
at a temperature of 313.15 K. That is, plot p vs x 1 and p vs y 1 in the same plot. In
your plot compare to the reference data freely available in the Dortmund Data
Bank. 5
2. Construct a T x y phase diagram for the binary system ethyl acetate(1)/ethanol(2)
at a pressure of 101.33 kPa. That is, plot T vs x 1 and T vs y 1 in the same plot. In
your plot compare to the reference data freely available in the Dortmund Data
Bank. 6 You should additionally construct a McCabe-Thiele (y 1 vs x 1 ) diagram.
5 http://www.ddbst.com/en/EED/VLE/VLE%20Ethanol%3BEthyl%20acetate.php
6 Note that the NRTL parameters we are using were regressed to reference data at 313.15 K.
Using them at a different temperature is an extrapolation. While NRTL does in theory account for
temperature dependence, you should in general proceed with caution.
310
Chapter 8 Systems of Equations
Exercise 8.7 (Two attempts of Professor Paluch discussing how to set-up the problem
can be found here and here.) Now that we have a grasp of CSTR basics, let’s consider the
case of multiple reactions. We will consider two scenarios. First we will have the case of
parallel reactions. For this case, the reactant (or reactants) is consumed by two or more
different reactions. Let’s consider the case:
A→B
(P.1)
A →C
(P.2)
The second case will be series reactions, in which the reactant (or reactants) form an
intermediate product which reacts further to form another product. For our exercise we
will consider the case:
A→B
(S.1)
B →C
(S.2)
In both cases, B will be our desired product and C will be an undesired product. When
quantifying reaction progress for the case of series and parallel reactions, a common
reaction progress variable is the selectivity of the desired relative to the undesired product.
Here we define the selectivity of B relative to C (or desired product relative to undesired
product) as
nB C B
S B /C =
=
nC C C
How does our mole balance design equation change? Well, before when we had a
single reaction we had
r A = νA r
Now, we just need to sum over each reaction. We will use a subscript 1 to correspond to
reaction 1, and a subscript 2 to correspond to reaction 2 in both the parallel (P) and series
(S) case. (Note that both the rate r and stoichiometic coefficient ν A will depend on the
reaction number. Also note that it is possible to have a stoichiometric coefficient of 0.)
This gives the net rate of reaction for the parallel and series case of:
r A = ν A,1 r 1 + ν A,2 r 2
For this problem, we need to know both n B and nC (or C B and CC ). Remember for
a single reaction the number of moles of each species is related by stoichiometry. This
will not be the case here with our multiple reactions. We will therefore have a system of
equations; a mole balance design equation for component B and C, and since our rate
expression is dependent on A, solve for A too. For both the parallel and series reaction
case we have:
r A = ν A,1 r 1 + ν A,2 r 2
r B = νB,1 r 1 + νB,2 r 2
rC = νC ,1 r 1 + νC ,2 r 2
For reaction 1, use the same parameters as before, namely,
r 1 = k 1C A
k 1 = k 0,1 exp[−E 1 /(RT )]
k 0,1 = 2.4 × 108 s−1
8.16 Exercises
311
E 1 = 15.3kcal/mol
For reaction 2 for the parallel reaction case (P.2),
r 2 = k 2C A
k 2 = k 0,2 exp[−E 2 /(RT )]
E 2 = 15.3kcal/mol
And for reaction 2 for the series reaction case (S.2),
r 2 = k 2C B
k 2 = k 0,2 exp[−E 2 /(RT )]
E 2 = 15.3kcal/mol
For the parallel and series reactions described, I would like you to plot the composition of each species versus the space time, the fractional conversion of A versus space
time, and the selectivity of B relative to C versus time. Consider the limits of very large
and very small space times when you think about selectivity.
For both the parallel and series case, I would like you to consider: k 0,2 = 0.1k 0,1 ,
k 0,2 = k 0,1 , and k 0,2 = 10k 0,1 . Compare the behavior of parallel and series reactions, and
discuss how you might “optimize” your reactor in both cases. Please summarize your
findings in a short report supported with plots.
Please note that you need to be careful when plotting selectivity. Component C is
a product, where initially only A is present. For the series case, B is the intermediate
product. Therefore, at very small times, you will have a very small amount of B present,
and a very very very small amount of C present. This can cause the value of the selectivity
to be very large. (Remember in the limit that the moles of C goes to zero, the selectivity
goes to infinity!) Once you get a feel for the range of values, you might consider setting
the y-axis limits. Or alternatively, you might consider excluding values are very small
space times.
Chapter
9
Ordinary Differential Equations
In Chapter 9 we learn how to numerically solve first order initial value ordinary
differential equations. Then in Chapter 10 we will solve systems of first order ordinary differential equations, and in Chapter 11 higher order differential equations.
By the end of this chapter you will be able to:
• Provide a basic explanation of how to numerically solve initial value ODEs
(Euler)
• Exhibit ability to construct “rate” functions
• Apply ode45 to solve initial value ODEs
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
9.1 Differential equations
A differential equation (DE) is an equation that describes the derivatives of an
unknown function. “Solving a DE” means finding a function whose derivatives
satisfy the equation.
For example, when bacteria grow in particularly bacteria-friendly conditions,
the rate of growth at any point in time is proportional to the current population.
What we might like to know is the population as a function of time. Toward that
end, let’s define f to be a function that maps from time, t , to population y. We
don’t know what it is, but we can write a differential equation that describes it:
df
=af
(9.1)
dt
where a is a constant that characterizes how quickly the population increases.
Notice that both sides of the equation are functions. To say that two functions
are equal is to say that their values are equal at all times. In other words:
313
314
Chapter 9 Ordinary Differential Equations
∀t :
df
(t ) = a f (t )
dt
This is an ordinary differential equation (ODE) because all the derivatives involved are taken with respect to the same variable. If the equation related derivatives with respect to different variables (partial derivatives), it would be a partial
differential equation. This equation is first order because it involves only first
derivatives. If it involved second derivatives, it would be second order, and so on.
This equation is linear because each term involves t , f or d f /d t raised to the
first power; if any of the terms involved products or powers of t , f and d f /d t it
would be nonlinear.
Linear, first order ODEs can be solved analytically; that is, we can express
the solution as a function of t . This particular ODE has an infinite number of
solutions, but they all have this form:
f (t ) = be at
For any value of b, this function satisfies the ODE. If you don’t believe me, take its
derivative and check.
If we know the population of bacteria at a particular point in time, we can use
that additional information to determine which of the infinite solutions is the
(unique) one that describes a particular population over time.
For example, if we know that f ((0) = 5 billion cells, then we can write
f (0) = 5 = be a0
and solve for b, which is 5. That determines what we wanted to know:
f (t ) = 5e at
The extra bit of information that determines b is called the initial condition
(although it isn’t always specified at t = 0). So to put it all together, we just solved
a linear, first order, initial value ODE.
Unfortunately, most interesting physical systems are described by nonlinear
DEs, most of which can’t be solved analytically. The alternative is to solve them
numerically. In this chapter we will solve first order, initial value ODEs. Then in a
couple of chapter we will solve second (and higher) order, initial value ODEs.
If you would like to learn more about DEs, the “Open Textbook Library” contains a number of excellent, freely available textbooks on the subject.1 For a quick
introduction or refresher geared toward engineering students, I would recommend “Notes on Diffy Qs: Differential Equations for Engineers”, by Jirí Lebl which
is actively updated and under development, and is freely available.
9.2 Euler’s method
315
9.2 Euler’s method
The simplest numerical method for ODEs is Euler’s method, named after the
Swiss mathematician Leonhard Paul Euler (1707–1783). If you need help getting
excited about Euler’s method, watch the clip from the motion picture “Hidden
Figures” at this link.
Here’s a test to see if you are as smart as Euler. Let’s say that you arrive at time
t and measure the current population, y, and the rate of change, r . What do you
think the population will be after some period of time ∆t has elapsed?
If you said y + r ∆t , congratulations! You just invented Euler’s method. This
estimate is based on the assumption that r is constant, but in general it’s not, so
we only expect the estimate to be good if r changes slowly and ∆t is small. That
is, we are assuming y changes linearly over the range t 0 to t 0 + ∆t so that its rate
of change (i.e., its derivative) is constant.
But let’s assume (for now) that the ODE we are interested in can be written so
that
df
(t ) = g (t , y)
dt
where g is some function that maps (t , y) onto r ; that is, given the time and
current population, it computes the rate of change. Then we can advance from
one point in time to the next using these equations:
Tn+1 = Tn + ∆t
(9.2)
F n+1 = F n + g (t , y) ∆t
(9.3)
Here {Ti } is a sequence of times where we estimate the value of f , and {F i } is the
sequence of estimates. For each index i , F i is an estimate of f (Ti ). The interval
∆t is called the time step.
Assuming that we start at t = 0 and we have an initial condition f (0) = y 0
(where y 0 denotes a particular, known value), we set T1 = 0 and F 1 = y 0 , and then
use Equations 9.2 and 9.3 to compute values of Ti and F i until Ti gets to the value
of t we are interested in.
If the rate doesn’t change too fast and the time step isn’t too big, Euler’s method
is accurate enough for most purposes. One way to check is to run it once with
time step ∆t and then run it again with time step ∆t /2. If the results are the same,
they are probably accurate; otherwise, cut the time step again.
Euler’s method is first order, which means that each time you cut the time
step in half, you expect the estimation error to drop by half. With a second-order
method, you expect the error to drop by a factor of 4; third-order drops by 8, etc.
The price of higher order methods is that they have to evaluate g more times per
time step.
After we have solved a few ODE’s using MATLAB’s built-in functionality, we
will revisit Euler’s method and see it in action in Section 9.10.
1 https://open.umn.edu/opentextbooks/subjects/mathematics
t If you think of our linear
interpolation example (or
a first order Taylor series
expansion), this is accurate if the function is linear
over the range ∆t . As ∆t becomes smaller and smaller,
all functions will look more
and more linear over the
range.
316
Chapter 9 Ordinary Differential Equations
9.3 Another note on notation
There’s a lot of math notation in this chapter so I want to pause to review what we
have so far. Here are the variables, their meanings, and their types:
Name
Meaning
Type
t
∆t
y
r
f
time
time step
population
rate of change
The unknown function specified,
implicitly, by an ODE.
The first time derivative of f
A “rate function,” derived from
the ODE, that computes rate of
change for any t , y.
a sequence of times, t , where
we estimate f (t )
a sequence of estimates for f (t )
scalar variable
scalar constant
scalar variable
scalar variable
function t → y
d f /d t
g
T
F
function t → r
function t , y → r
sequence
sequence
So f is a function that computes the population as a function of time, f (t ) is the
function evaluated at a particular time, and if we assign f (t ) to a variable, we
usually call that variable y.
Similarly, g is a “rate function” that computes the rate of change as a function
¡ ¢
of time and population. If we assign g t , y to a variable, we call it r .
d f /d t is the first derivative of f , and it maps from t to a rate. If we assign
d f /d t (t ) to a variable, we call it r .
It is easy to get d f /d t confused with g , but notice that they are not even
the same type. g is more general: it can compute the rate of change for any
(hypothetical) population at any time; d f /d t is more specific: it is the actual rate
of change at time t , given that the population is f (t ).
This still might be a little confusing. No worries, when we start to work
examples, this should all get resolved.
9.4 ode45
t If you are using GNU Octave, you will need to install the “ode” package.
You will then need to add
pkg load ode to your
scripts or execute it from
the Command Window before
using ode45. If you are unsure if you currently have
the ode package installed,
execute pkg list from the
Command Window .
A limitation of Euler’s method is that the time step is constant from one iteration
to the next. But some parts of the solution are harder to estimate than others; if
the time step is small enough to get the hard parts right, it is doing more work than
necessary on the easy parts. The ideal solution is to adjust the time step as you
go along. Methods that do that are called adaptive, and one of the best adaptive
methods is the Dormand-Prince pair of Runge-Kutta formulas. You don’t have to
know what that means, because the nice people at Mathworks have implemented
it in a function called ode45. The ode stands for “ordinary differential equation
[solver];” the 45 indicates that it uses a combination of 4th and 5th order formulas.
9.4 ode45
317
In order to use ode45, you have to write a MATLAB function that evaluates g as
a function of t and y. As an example, suppose that the rate of population growth
for rats depends on the current population and the availability of food, which
varies over the course of the year. The governing equation might be something
like
df
((t ) = a f (t ) [1 + sin (ωt )]
dt
(9.4)
where t is time in days and f (t ) is the population at time t . a and ω are parameters. A parameter is a value that quantifies a physical aspect of the scenario being
modeled. For example, in Example 7.2 we used parameters rho and r to quantify
the density and radius of a duck. Parameters are often constants, but in some
models they vary in time. In this example, a characterizes the reproductive rate,
and ω is the frequency of a periodic function that describes the effect of varying
food supply on reproduction.
This equation specifies a relationship between a function and its derivative.
In order to estimate values of f numerically, we have to transform it into a rate
function. The first step is to introduce a variable, y, as another name for f (t )
df
(t ) = a y [1 + sin (ωt )]
dt
This equation means that if we are given t and y, we can compute d f /d t (t ),
which is the rate of change of f . The next step is to express that computation as a
function called g :
¡ ¢
g t , y = a y [1 + sin (ωt )]
Writing the function this way is useful because we can use it with Euler’s method
or ode45 to estimate values of f . All we have to do is write a MATLAB function that
evaluates g . Here’s what that looks like using the values a = 0.01 and ω = 2π/365
(one cycle per year):
Listing 9.1 rats.m
1
2
3
4
5
function res = rats(t, y)
a = 0.01;
omega = 2 * pi / 365;
res = a * y * (1 + sin(omega * t));
end
You can test this function from the Command Window by calling it with different
values of t and y; the result is the rate of change (in units of rats per day):
>> r = rats(0, 2)
r = 0.0200
t Our bacteria growth equation (eq. 9.1) and rat growth
equation (eq. 9.4) should
look very similar. In the rat
problem we multiply a f by
a term that oscillates with t
to model the change in food
supply with seasons.
318
Chapter 9 Ordinary Differential Equations
So if there are two rats on January 1, we expect them to reproduce at a rate that
would produce 2 more rats per hundred days. But if we come back in April, the
rate has almost doubled:
>> r = rats(120, 2)
r = 0.0376
Since the rate is constantly changing, it is not easy to predict the future rat
population, but that is exactly what ode45 does. Here’s how you would use it:
t Something we will do in
>> ode45(@rats, [0, 365], 2)
The first argument is a handle for the function that computes g , the rate. The
second argument is the interval we are interested in, one year. The third argument
is the initial population, f (0) = 2.
When you call ode45 without assigning the result to a variable, MATLAB displays the result in a figure. Giving the figure a title and labeling the axis:
>> title('rats')
>> xlabel('day')
>> ylabel('Number of rats')
rats
80
70
60
Number of rats
later examples, you might
store the time interval and
the initial condition to variables, say t_range and ic,
to clarify your code.
50
40
30
20
10
0
0
50
100
150
200
250
300
350
400
day
Figure 9.1 ode45(@rats,[0,365],2)
The x-axis shows time in days, with our data spanning the range 0 to 365 days;
the y-axis shows the rat population, which starts at 2 and grows to almost 80. The
9.5 Multiple output variables
rate of growth is slow in the winter and summer, and faster in the spring and fall,
but it also accelerates as the population grows.
Within this chapter I will define parameters within the rate function M-file.
However, remember that this is a case where the use of anonymous functions
may be advantageous. If we wanted to look at the effect of the parameters a or ω
on the solution, we would update their value in the rate function, save, and then
re-run. We could alternatively create a general rate function that takes as input
variables the parameters a and ω:
Listing 9.2 rats_general.m
1
2
3
4
5
function res = rats_general(t, y, a, omega)
%a = 0.01;
%omega = 2 * pi / 365;
res = a * y * (1 + sin(omega * t));
end
This rate function can not be used directly with ode45 since it expects a (rate)
function with input variables of just t and y. We can overcome this limitation
using an anonymous function:
>>
>>
>>
>>
a = 0.01;
omega = 2 * pi / 365;
rats_gen = @(t,y) rats_general(t,y,a,omega);
ode45(rats_gen, [0, 365], 2)
In doing so we find we obtain the same result. Now if you wished to change the
value of one of the parameters, we could update its value in the Command Window ,
re-define rats_gen, and then re-solve.
9.5 Multiple output variables
ode45 is one of many MATLAB functions that return more than one output variable. The syntax for calling it and saving the results is
>> [T, Y] = ode45(@rats, [0, 365], 2);
The first return value is assigned to T; the second is assigned to Y. Each element
of T is a time, t , where ode45 estimated the population; each element of Y is an
estimate of f (t ). The variables T and Y are column vectors.
If you assign the output values to variables, ode45 doesn’t draw the figure;
you have to do it yourself:
>> plot(T, Y, 'bo-')
319
320
Chapter 9 Ordinary Differential Equations
>> title('rats')
>> xlabel('day')
>> ylabel('Number of rats')
The resulting plot is the same as fig. 9.1. In the plot you will notice that the space
between the points is not quite even. They are closer together at the beginning of
the interval and farther apart at the end.
To see the population at the end of the year, you can display the last element
from each vector:
>> [T(end), Y(end)]
ans = 365.0000
76.9530
end is a special word in MATLAB; when it appears as an index, it means “the
index of the last element.” You can use it in an expression, so Y(end-1) is the
second-to-last element of Y.
How much does the final population change if you double the initial population? How much does it change if you double the interval to two years? How
much does it change if you double the value of a?
9.6 Analytic or numerical?
When you solve an ODE analytically, the result is a function, f , that allows you
to compute the population, f (t ), for any value of t . When you solve an ODE
numerically, you get two vectors. You can think of these vectors as a discrete approximation of the continuous function f : “discrete” because it is only defined for
certain values of t , and “approximate” because each value F i is only an estimate
of the true value f (t ). So those are the limitations of numerical solutions. The
primary advantage is that you can compute numerical solutions to ODEs that
don’t have analytic solutions, which is the vast majority of nonlinear ODEs.
If you are curious to know more about how ode45 works, you can modify rats
to display the points, (t , y), where ode45 evaluates g . Here is a simple version:
t Later in this chapter we
will see how we can make
our numerical solution
behave like an analytic solution, and how we can
use fzero to find the time
when we have a specific
value. Later, we will additionally see how we can use
MATLAB’s interpolating
function, interp1, along
with ode45 too. Armed
with these tricks, you might
Listing 9.3 rats_2.m
forget about analytical solutions all together.
1 function res = rats_2(t, y)
2
3
4
5
6
end
plot(t, y, 'rx')
a = 0.01;
omega = 2 * pi / 365;
res = a * y * (1 + sin(omega * t));
Each time rats_2 is called, it plots one data point; in order to see all of the data
points, you have to use hold on. To make the resulting plot clear and readable,
9.7 What can go wrong?
321
we will consider the shorter range of 0 to 70 days.
>>
>>
>>
>>
clf
hold on
[T, Y] = ode45(@rats_2, [0, 70], 2);
plot(T,Y,'bo')
6
5.5
5
4.5
4
3.5
3
2.5
2
0
10
20
30
40
50
60
70
Figure 9.2 ode45(@rats_2,[0, 70],2)
The red × show the points where ode45 called rats. The blue ◦ show where
ode45 estimates f (t ). Notice that ode45 typically evaluates g several times for
each estimate. This allows it to improve the estimates, for one thing, but also to
detect places where the errors are increasing so it can decrease the time step (or
the other way around).
9.7 What can go wrong?
Don’t forget the @ on the function handle. If you leave it out, MATLAB treats the
first argument as a function call, and calls rats without providing arguments.
>> ode45(rats, [0,365], 2)
Error using rats (line 4)
Not enough input arguments.
Again, the error message is confusing, because it looks like the problem is in rats.
You’ve been warned! The exception to this is if you use an anonymous function
t This is similar to the restriction of the number of input
variables that can be passed
that we saw previously with
fzero and fsolve. We
have already seen that we
can get around this limitation using anonymous
functions. Later we will see
how we could use nested
functions too.
322
Chapter 9 Ordinary Differential Equations
to pass additional parameters, where an anonymous function is already of type
function handle.
Also, remember that the function you write will be called by ode45, which
means it has to have the signature ode45 expects: it should take two input variables, t and y, in that order, and return one output variable, r.
If you are working with a rate function like this for our bacteria growth problem (eq. 9.1):
g (t , y) = a y
You might be tempted to write this:
1
2
3
4
function res = rate_func(y)
a = 0.1;
res = a * y;
end
% WRONG
But that would be wrong. So very wrong. Why? Because when ode45 calls
rate_func, it provides two arguments. If you only take one input variable, you’ll
get an error. So you have to write a function that takes t as an input variable, even
if you don’t use it.
1
2
3
4
function res = rate_func(t, y)
a = 0.1;
res = a * y;
end
% RIGHT
Another common error is to write a function that doesn’t make an assignment
to the output variable. If you write something like this:
1
2
3
4
5
function res = rats(t, y)
a = 0.01;
omega = 2 * pi / 365;
r = a * y * (1 + sin(omega * t));
end
And then call it from ode45, you get
% WRONG
9.8 Stiffness
323
>> ode45(@rats, [0,365], 2)
Error using feval
Undefined function 'rats' for input arguments of type 'double'.
Error in odearguments (line 87)
f0 = feval(ode,t0,y0,args{:});
% ODE15I sets args{1} to yp0.
Error in ode45 (line 113)
[neq, tspan, ntspan, next, t0, tfinal, tdir, y0, f0, odeArgs,
odeFcn, ...
This might be a scary message, but if you read the first line and ignore the rest,
you’ll get the idea.
Yet another mistake that people make with ode45 is leaving out the brackets
on the second argument. In that case, MATLAB thinks there are four arguments,
and you get
>> ode45(@rats, 0, 365, 2)
Error using odearguments (line 21)
When the first argument to ode45 is a function handle, the
tspan argument must have at least two elements.
Error in ode45 (line 113)
[neq, tspan, ntspan, next, t0, tfinal, tdir, y0, f0, odeArgs,
odeFcn, ...
Again, if you read the first line, you should be able to figure out the problem
(tspan stands for “time span”, which we have been calling the interval).
9.8 Stiffness
There is yet another problem you might encounter, but if it makes you feel better,
it might not be your fault: the problem you are trying to solve might be stiff.2 I
won’t give a technical explanation of stiffness here, except to say that for some
problems (over some intervals with some initial conditions) the time step needed
to control the error is very small, which means that the computation takes a long
time. Here’s one example:
df
= f 2− f 3
dt
If you solve this ODE with the initial condition f (0) = δ over the interval from 0 to
2/δ, with δ = 0.01, you should see something like this:
2 The following discussion is based partly on an article from Mathworks available at https:
//www.mathworks.com/company/newsletters/articles/stiff-differential-equations.html
t Computing speed and the
efficiency of MATLAB keeps
improving, so this example
might not be too bad on
your computer. Note, when
MATLAB is struggling, a
Stop icon will show up in
the bottom left corner of
the plot window. You can
click it to kill the process.
You can also click Ctrl + c
from the command line to
kill a process. You may notice Stop buttons in a few
plots in this chapter where
we try computationally
intensive processes.
324
Chapter 9 Ordinary Differential Equations
Listing 9.4 stiff.m
1
2
3
function res = stiff(t, y)
res = y^(2)-y^(3);
end
>> delta = 0.01;
>> ode45(@stiff, [0,2/delta], delta)
1.2
1
0.8
0.6
0.4
0.2
0
0
50
100
150
200
Figure 9.3 ode45(@stiff,[0, 2/delta] delta)
t Again, when I first taught
this course in Spring 2017
the calculation was very
slow. Now it is not and
ode45 currently works perfectly fine.
After the transition from 0 to 1, the time step is very small and the computation
goes slowly. For smaller values of δ, the situation is even worse. In this case, the
problem is easy to fix: instead of ode45 you can use ode23s, an ODE solver that
tends to perform well on stiff problems (that’s what the “s” stands for).
In general, if you find that ode45 is taking a long time, you might want to try
one of the stiff solvers. It won’t always solve the problem, but if the problem is
stiffness, the improvement can be striking.
Additional information about solving ODEs with MATLAB and the different
available ODE solvers can be found in the MATLAB article “Choose an ODE
Solver”. But I emphasize that ode45 should work for most of the problems you
will encounter, and should be your default ODE solver. You may also find the the
MATLAB articles “Solve Nonstiff ODEs” and “Solve Stiff ODEs” useful.
9.9 Examples
325
9.9 Examples
The best way to get comfortable using ode45 is to solve some problems. And the
best problems to practice on are ones that you know the answer to. In addition
to the examples here, for more practice I would encourage you to solve some of
the example problems with analytical solutions provided in “Notes on Diffy Qs:
Differential Equations for Engineers”.
Example 9.1 (Link to screen cast with accompanying M-files.) Pretend you
dropped an object from a high stationary platform (or from an airplane).
Let’s model the downward velocity of the object as a function of time. If
frictional resistance is ignored, we have
ma(t ) = m
dv
(t ) = mg
dt
(9.5)
where a is the acceleration of the object, v is the downward velocity, t is
time, g is the acceleration due to gravity, and m is the mass. If we account
for the frictional force on the object due to air, which acts opposite in
direction to g , and we assume that the frictional force is proportional to v,
performing a force balance we have
ma(t ) = m
dv
(t ) = mg − γv(t )
dt
(9.6)
where γ is our coefficient of friction. (We previously included m in our
expression to facilitate the writing of our force balance.)
Dividing through by m and letting k = γ/m, we have
dv
(t ) = g − kv(t )
dt
(9.7)
subject to the initial condition
v(0) = 0
(9.8)
We could solve this expression using separation of variables and obtain the
analytic result
v(t ) =
´
g³
1 − e −kt
k
(9.9)
Let’s solve the differential equation numerically and compare to the analytic
solution. To solve, let g = 9.81 m/s2 and k = 0.5 s−1 . To solve, let’s first create
our function. We start by re-writing the expression replacing v(t ) with y
326
Chapter 9 Ordinary Differential Equations
dv
(t ) = g − k y
dt
Next, replace
dv
d t (t ) with
(9.10)
g (t , y)
g (t , y) = g − k y
(9.11)
Careful not to confuse g (t , y) with parameter g . We are now set to write our
function and solve over the range 0 to 20 seconds.
Listing 9.5 free_fall.m
2
3
4
5
function res = free_fall(t, y)
k=0.5;
g=9.81;
res = g-k*y;
end
>>
>>
>>
>>
>>
[T,Y] = ode45(@free_fall, [0,20], 0);
hold on
plot(T,Y,'ko')
xlabel('t/s')
ylabel('position/m')
20
18
16
14
position/m
1
12
10
8
6
4
2
0
0
5
10
15
t/s
Figure 9.4 ode45(@free_fall, [0,20], 0)
20
9.9 Examples
327
Okay, and now let’s compare to the analytic solution.
Listing 9.6 free_fall_soln.m
2
3
4
5
function res = free_fall_soln(t)
k = 0.5;
g = 9.81;
res = (g/k)*(1-exp(-k*t));
end
>> fplot(@free_fall_soln,[0,20],'-r')
20
18
16
14
position/m
1
12
10
8
6
4
2
0
0
5
10
15
20
t/s
Figure 9.5 Comparing the numerical and analytic solution.
A perfect match! This is also a cool problem because after about 10 s we see
the velocity appears to plateau out at a constant value. When the velocity
becomes constant, the acceleration is 0 and we call this the terminal velocity.
This is what makes skydiving so much fun; if the acceleration is 0, the net
force is also 0.
As an aside, while I created a separate M-file for free_fall_soln, this is a
perfect case when using an anonymous function could be convenient.
>>
>>
>>
>>
k = 0.5;
g = 9.81;
free_fall_soln = @(t) (g/k)*(1-exp(-k*t));
fplot(free_fall_soln,[0,20],'-r');
328
Chapter 9 Ordinary Differential Equations
Example 9.2 (Link to screen cast with accompanying M-file.) The first
differential equation I ever remember solving was the draining of a tank. I
also remember solving this problem when I taught Fluid Mechanics.
Imagine we have a cylindrical tank filled with a liquid. Then at time t = 0 a
valve is opened at the bottom of the tank allowing the liquid to drain. We
can perform a mass balance on the tank just like in your Mass and Energy
Balance course.
rate of accumulation − rate of consumption = flow in − flow out
(9.12)
Which for our case reduces to
rate of accumulation = −flow out
(9.13)
dm
(t ) = −ṁ(t )
dt
(9.14)
where m is the total mass of fluid in the tank, t is time, and ṁ is the mass
flow rate of fluid out of the tank. Assuming constant uniform density (ρ)
¡ ¢
d ρV
dt
(t ) = −ρQ(t )
dV
(t ) = −Q = −av(t )
dt
(9.15)
(9.16)
where in the second expression we have canceled out ρ, and Q is the volumetric flow rate which we can relate to the velocity of the fluid at the exit
(v) and the area of the hole through which the fluid is draining.
Performing an energy balance, we can relate the kinetic energy of the fluid
at the exit to the potential energy of the fluid at the top of the tank
1
mg h = mv 2
2
(9.17)
where g is the acceleration due to gravity and h is the height of the fluid in
the tank. Solving for v
¡
¢1/2
v = 2g h
(9.18)
Before substituting back in, let’s relate V to h. For a cylindrical tank of
constant cross sectional area, V = Ah, where A is the cross sectional area.
Since A is constant, it can be taken out of the differential. This results in
9.9 Examples
329
A
¡ ¢1/2
¡
¢1/2
dh
h(t )1/2
= −a 2g
(t ) = −a 2g h(t )
dt
¡ ¢1/2
−a 2g
dh
(t ) =
h(t )1/2
dt
A
(9.19)
(9.20)
If we were to solve analytically using separation of variables, subject to the
initial condition h(0) = h 0 , the result is
h(t ) =
µp
h0 − k
t
2
¶2
(9.21)
¡ ¢1/2
where k = a 2g
/A.
Let’s solve numerically. Start by replacing h(t ) with y
¡ ¢1/2
−a 2g
dh
(t ) =
y 1/2
dt
A
Next, replace
dh
d t (t ) with
(9.22)
g (t , y)
g (t , y) =
¡ ¢1/2
−a 2g
A
y 1/2
(9.23)
We can now write our function
Listing 9.7 tank_drain.m
1
2
3
4
5
6
7
8
function res = tank_drain(t,y)
%a = 10/(100^(2)); % 10 cm^2
%A = 5; % 5 m^2
%g = 9.81;
%k = a*sqrt(2*g)/A;
k = 0.4;
res = -k*sqrt(y);
end
In writing the function, I have lumped all of the parameters into k and have
set its value to 0.4 for the purpose of this problem. Solving
>>
>>
>>
>>
>>
hold on
[T,Y] = ode45(@tank_drain, [0,20], 20);
plot(T,Y,'ko')
xlabel('time')
ylabel('height')
330
Chapter 9 Ordinary Differential Equations
20
18
16
14
height
12
10
8
6
4
2
0
0
5
10
15
20
time
Figure 9.6 ode45(@tank_drain, [0,20], 20)
Where the last input to ode45 is our initial condition h(0) = 20 m which I
have arbitrarily chosen for our solution. Okay, and now let’s compare to the
analytic solution.
Listing 9.8 tank_drain_soln.m
1
2
3
4
5
function res = tank_drain_soln(t)
k = 0.4;
h0 = 20;
res = (sqrt(h0)-k*t./2).^(2);
end
>> fplot(@tank_drain_soln,[0,20],'-r')
9.10 Euler in action!
331
20
18
16
14
height
12
10
8
6
4
2
0
0
5
10
15
20
time
Figure 9.7 Comparing the numerical and analytic solution.
A perfect match!
Once again, I could just as well use an anonymous function for this last part.
>>
>>
>>
>>
k = 0.4;
h0 = 20;
tank_drain_soln = @(t) (sqrt(h0)-k*t./2).^(2);
fplot(tank_drain_soln,[0,20],'-r');
9.10 Euler in action!
Now that we have solved a few examples using MATLAB’s built-in function ode45,
let’s revisit Euler’s method. The purpose of this is the same as when we considered
the bisection method in Section 7.17.1. When available, you should use MATLAB’s
built-in functions. They are bug free and optimized for efficiency. But it is good to
look at some “simple” numerical methods to gain an understanding of what is
going on under the hood, and to appreciate what MATLAB is doing for us.
We already described Euler’s method in Section 9.2. Writing a MATLAB function to perform Euler’s method we have:
332
Chapter 9 Ordinary Differential Equations
Listing 9.9 euler_ode.m
1 function [T,Y] = euler_ode(fcn_handle, Trange, dt, y0)
2
%
3
% Un-packing Trange. The first element is t0 and the second is Tfinal
4
t0 = Trange(1);
5
tfinal = Trange(2);
6
7
% With a fixed timestep, we can create a vector of times at which we
8
% will evaluate our function. I will then take the transpose to get a
9
% column vector like ode45.
10
T = t0:dt:tfinal;
11
T = T';
12
13
% Pre-size our Y vector, f(t). I will make it a column vector too just
14
% like ode45.
15
Y = zeros(length(T),1);
16
%
17
% Store the initial condition to Y(1)
18
Y(1) = y0;
19
20
% Looping over all times. We will start with i=2 since i=1 just
21
% corresponds to our initial conditions. I could start at i=1, but then
22
% need to shift my indices.
23
for i=2:length(T)
24
dy = fcn_handle(T(i-1),Y(i-1));
25
Y(i) = dy*dt+Y(i-1);
26
end
27
28 end
Now let’s apply it to solve the rat growth problem (Listing 9.1).
>>
>>
>>
>>
hold on
t0=0; tfinal=365; dt=1; y0=2;
[T1,Y1]=euler_ode(@rats,[t0,tfinal],dt,y0);
plot(T1,Y1,'r-')
We are using a time step of 1 day, and are plotting the solution as a red line. Note
that in euler_ode we are returning two vectors, T and Y; if you wish to return
multiple variables from a function, we use the bracket notation as done here. Let’s
also try a time step of 0.1 days and plot the result as a green line.
>> dt=0.1;
>> [T2,Y2]=euler_ode(@rats,[t0,tfinal],dt,y0);
>> plot(T2,Y2,'g-')
Lastly, let’s compare our answer to that using ode45, where I will plot the solution
as blue circles.
9.10 Euler in action!
333
>> [T3,Y3] = ode45(@rats, [t0, tfinal], y0);
>> plot(T3,Y3,'bo')
Notice that with ode45 we do not need to specify a time step, it is an adaptive
method. All of the other required inputs are the same.
The plot comparing our results is shown below. We find that using Euler’s
method with a timestep of 1 day, we are in good agreement with ode45 at short
times, but then the disagreement increases as time increases. Initially, the error is
very small and not noticeable. The error then grows because F n+1 is dependent
on F n , the value at the previous integration step. This causes errors to propagate
and grow.
When we decrease the time step to 0.1 days, the agreement is excellent.
80
70
60
50
40
30
20
10
0
0
50
100
150
200
250
300
350
400
Figure 9.8 Comparing Euler’s method and ode45 to solve the rat growth
problem.
The error in Euler’s method is proportional to the time step size. So we can
always decrease the value of the time step and improve its accuracy. But this
comes at a greater computational cost.
>> [T3,Y3] = ode45(@rats, [t0, tfinal], y0);
>> length(Y3)
ans = 45
Using ode45 we have the initial condition plus 44 integration steps, but remember
these higher order schemes are evaluating g more times per step than this number
reveals. Using Euler’s method with a timestep of 1 day we have the initial condition
plus 365 integration steps, and with a time step of 0.1 days we have the initial
334
Chapter 9 Ordinary Differential Equations
condition plus 3,650 integration steps! Be that as it may, Euler’s method is rather
robust and efficient, so at times you may find it a good choice to use.
To highlight the speed and robustness of Euler’s method, let’s look at our “stiff”
problem (Listing 9.4). Let’s consider time steps of size delta and delta/10. And
we can compare to ode23s for completeness.
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
hold on
delta = 0.01; t0=0; tfinal=200; y0=delta;
dt = delta;
[T1,Y1] = euler_ode(@stiff,[t0,tfinal],dt,y0);
plot(T1,Y1,'r-')
dt = delta/10;
[T2,Y2] = euler_ode(@stiff,[t0,tfinal],dt,y0);
plot(T2,Y2,'g-')
[T3,Y3] = ode23s(@stiff, [t0,tfinal], y0);
plot(T3,Y3,'bo')
1.2
1
0.8
0.6
0.4
0.2
0
0
50
100
150
200
Figure 9.9 Comparing Euler’s method and ode23s to solve our “stiff” ODE.
A perfect match! And both sets of Euler calculations computed very quickly.
As a final point, let me just mention that methods like Euler’s method with
fixed time steps have some real applicability, and is something my research group
uses routinely. My group frequently uses a technique called molecular dynamics,
where we are literally solving Newton’s equations of motion at the molecular level.
However, a key requirement of our integration scheme (among others) is that it is
time reversible. That is, from a given point we can rewind back to the beginning.
Fixed time step methods satisfy this condition, while adaptive methods do not.
9.11 Return of fzero
There are many other cases where reversibility is important.
I guess that’s enough Euler fun... for now!
9.11 Return of fzero
Within this chapter we solved three first order ODEs corresponding to physical
processes. We have modeled the population or rats, the velocity of a falling object,
and the height of fluid draining from a tank. For our solution, we have plotted
the value of the function over a period of time, and we have seen how to store
the numerical results to vectors. When solving physical problems, we often want
to solve for the time at which the function is equal to a particular value. Before
moving on to the next chapter, let’s discuss how to accomplish this. We will start
by using a logical vector to identify elements satisfying a given condition. (Note,
a for loop would be possible too.) We will then see how we can use fzero to
obtain a precise result. In this later approach we will also see how we can compute
the value of the function at exact values of t . This gives us the ability to use our
numerical solution much like we might use an analytic solution.
Let’s return to our rat example discussed in Section 9.4. At what time do we
have 30 rats? From our plot, it looks like we have 30 rats at around 150 days. But I
would like a precise result. Solving as:
>>
>>
>>
>>
t0 = 0;
tfinal = 365;
y0 = 2;
[T,Y] = ode45(@rats, [t0, tfinal], y0);
we obtain two vectors, T and Y. T is a vector containing the times at which our
function was evaluated, where the first entry is t0 and the final entry is tfinal.
Y is a vector containing the values of the function at the corresponding time in
vector T. We know that the population is increasing from a value of y0 at time
t0. The first approach we could therefore apply to find when the population is
equal to 30 would be to perform a logical comparison to see when Y is greater
than or equal to 30. The resulting logical vector could then be used to identify
all of the elements of Y that are greater than or equal to 30. We would then take
the first element of the resulting vector as an estimate of the solution. The first
element because the population is increasing with time. Note, we do not perform
a logical comparison to see when Y is equivalent to 30 since ode45 is solving for
the population at discrete times, so an exact value of 30 between t0 and tfinal
is unlikely. Let’s do it!
>> Ysol = Y(Y>=30);
>> yans = Ysol(1)
yans = 32.8834
335
336
Chapter 9 Ordinary Differential Equations
>> TSol = T(Y>=30);
>> tans = TSol(1)
tans = 166.0951
We see that the population is not exactly 30, but rather 32.8834. We could of course
find a more precise result. One way would be to perform a linear interpolation
between this point and the previous point that would have been just less than 30.
We will return to this point in a couple of chapters when we look at MATLAB’s
built-in interpolating functions. Here, we will use what we know and use fzero.
Think back to Chapter 7 and the use of fzero and our bisection code to find
the zero of a (linear or) non-linear function over a given range. How did these
methods work? Assume that t is our independent variable. We first need to know
two values of t that bracket when the function (or dependent variable) is equal
to 0. We evaluate the function at these two points, and then guess a value of t
(between our other two t ’s that bracket the solution) where the zero occurs, and
evaluate the function at this point. We then check to see which of the original
t ’s bracket the zero with this new guessed value of t . We keep repeating and
shrinking the range until we converge on our answer.
So in order to be able to use fzero, we need a function that we pass a value of
t , and it returns the value of the function. We can do this. If I use ode45 to solve
our ODE over the range 0 to t , and store the results to a vector, the final value of
the vector is the value of the function at t . Let’s create a new function rat_time
to do just that.
Listing 9.10 rat_time.m
1
2
3
4
5
6
7
8
function res = rat_time(t)
t0 = 0;
% initial time
tfinal = t;
% final time
y0 = 2;
% initial population
[T,Y] = ode45(@rats, [t0, tfinal], y0);
% now let's return the the population at t
res = Y(end);
end
And testing its functionality
>> rat_time(150)
ans = 26.2171
Nice! So now we have a method to compute the value of the function at an exact
time. For fzero, we can only search for where a function is equal to zero. If we
wish to find where the population is equal to 30, we should therefore subtract
30 from the value of the population so that when the population is equal to 30,
9.11 Return of fzero
337
the result of our function is 0. Since the population is increasing, we can be sure
it satisfies the conditions required by fzero. (Think of the bisection method.)
Updating our function and then testing:
Listing 9.11 rat_time_2.m
1
2
3
4
5
6
7
8
function res = rat_time_2(t)
t0 = 0;
% initial time
tfinal = t;
% final time
y0 = 2;
% initial population
[T,Y] = ode45(@rats, [t0, tfinal], y0);
% now let's return the the population at
res = Y(end)-30;
end
- 30
>> rat_time_2(150)
ans = -3.7829
Okay. So now let’s use fzero to search over the range 0.1 to 365 for when the
function is 0. Note that we can not begin our search at 0 because this would
result in calling ode45 with an initial and final time of 0, which results in an error.
(There would be nothing to integrate!)
>> fzero(@rat_time_2,[0.1,365])
ans = 159.2232
We get a precise answer of 159.2232 days. We can check by computing the population at this time.
>> rat_time(ans)
ans = 30.0000
Beautiful! We find that the answer found from our simple search of 166.0951 was
a reasonable estimate, but it is in error.
When solving, note that I created rat_time_2 as its own M-file. This is another case where it may be attractive to instead use an anonymous function.
>> rat_time_2 = @(t) rat_time(t) - 30;
>> fzero(rat_time_2,[0.1,365])
ans = 159.2232
Nice! Then if we wanted to look at the time to reach a different population, we
could just re-define rat_time_2 from the Command Window .
Having seen how we can use fzero with ode45, let’s apply our new skill to
338
Chapter 9 Ordinary Differential Equations
Examples 9.1 and 9.2. We will also consider a new example, modeling the cooling
of a cup of coffee.
Example 9.3 (Link to screen cast with accompanying M-file.) Let’s revisit
our free fall example, Example 9.1. What is the terminal velocity. Additionally, at what time is the velocity equal to 15 m/s?
Let’s start by writing a function to compute the velocity at a specified time.
Listing 9.12 free_fall_time.m
1
2
3
4
5
6
7
8
function res = free_fall_time(t)
t0 = 0; % initial time
tfinal = t; % final time
y0 = 0; % initial velocity
% solving our ODE
[T,Y]=ode45(@free_fall,[t0,tfinal],y0);
res = Y(end);
end
From our graph, we see that after about 15 s the velocity reaches a constant
value. Put differently, the derivative of the velocity (and hence the acceleration) is equal to zero. When this occurs we have reached our terminal
velocity.
>> free_fall_time(15)
ans = 19.6091
>> free_fall_time(20)
ans = 19.6191
>> free_fall_time(30)
ans = 19.6200
>> free_fall_time(40)
ans = 19.6200
>> free_fall_time(400)
ans = 19.6164
>> free_fall_time(500)
ans = 19.6160
>> free_fall_time(1000)
ans = 19.6136
>> free_fall_time(10000)
ans = 19.6113
>> free_fall_time(100000)
ans = 19.6113
>> free_fall_time(1e6)
9.11 Return of fzero
339
ans = 19.6157
>> free_fall_time(1e7)
ans = 19.6118
It appears to take a very long time to reach our terminal velocity, although
this small fluctuation may be attributed to numerical error. Nonetheless,
the rate of change and hence acceleration is very small (if not 0), so if this
was a skydiving problem, it would still be tons of fun. Note that if we were
to analytically solve for the terminal velocity as the point when d v/d t = 0,
we have v = 19.6200. Two more notes on this, is d v/d t is only a function of
v. We could therefore plug values of v into our rate function and find when
the rate (d v/d t = 0) is zero. Taking it a step further, we could use fzero on
a modified rate function where we do not take t as an input.
Now let’s find the time when the velocity is equal to 15 m/s. We need to
update free_fall_time so that it returns a value of zero when the velocity
is equal to 15 m/s.
Listing 9.13 free_fall_time_2.m
1
2
3
4
5
6
7
8
function res = free_fall_time_2(t)
t0 = 0; % initial time
tfinal = t; % final time
y0 = 0; % initial velocity
% solving our ODE
[T,Y]=ode45(@free_fall,[t0,tfinal],y0);
res = Y(end)-15;
end
Let’s search for our solution over the range 0.1 to 30 s. Remember, we can
not have a lower-bound of 0 because this would result in a call to ode45
with an initial and final time of 0.
>> fzero(@free_fall_time_2,[0.1,30])
ans = 2.8923
So the velocity reaches 15 m/s at 2.8923 s.
If we had instead wished to use an anonymous function:
>> free_fall_time_2 = @(t) free_fall_time(t) - 15;
>> fzero(free_fall_time_2,[0.1,30])
ans = 2.8923
340
Chapter 9 Ordinary Differential Equations
Example 9.4 Now let’s revisit our tank draining example, Example 9.2. Find
the time at which the height of the fluid in the tank is 15, 10, 5 and 1 m.
Let’s start by creating a function that computes the height of the fluid in the
take at a specified t . We then need to modify the function so that it evaluates to zero at these heights. Since we wish to look at 4 different heights, I
will write a general form of the function in which I can also pass the variable
yfinal which corresponds to the desired final height. I will then use an
anonymous function for each case to pass the the desired value of yfinal.
Listing 9.14 tank_drain_time.m
1
2
3
4
5
6
7
function res = tank_drain_time(t,yfinal)
t0 = 0; % initial time
tfinal = t; % final time
y0 = 20; % the initial heigh
[T,Y]=ode45(@tank_drain,[t0,tfinal],y0);
res = Y(end)-yfinal;
end
And now solving:
>> tank_drain_time_15 = @(t) tank_drain_time(t,15);
>> t15 = fzero(tank_drain_time_15,[0.1,20])
t15 = 2.9958
>> tank_drain_time_10 = @(t) tank_drain_time(t,10);
>> t10 = fzero(tank_drain_time_10,[0.1,20])
t10 = 6.5493
>> tank_drain_time_5 = @(t) tank_drain_time(t,5);
>> t5 = fzero(tank_drain_time_5,[0.1,20])
t5 = 11.1803
>> tank_drain_time_1 = @(t) tank_drain_time(t,1);
>> t1 = fzero(tank_drain_time_1,[0.1,20])
t1 = 17.3607
And so on.
9.11 Return of fzero
341
Example 9.5
Suppose that you are given an 8 ounce cup of coffee at 90 ◦ C and a 1 ounce
container of cream at room temperature, which is 20 ◦ C. You have learned from
bitter experience that the hottest coffee you can drink comfortably is 60 ◦ C.
Assuming that you take cream in your coffee, and that you would like to start
drinking as soon as possible, are you better off adding the cream immediately or
waiting? And if you should wait, then how long?
To answer this question, you have to model the cooling process of a hot liquid in
air. Hot coffee transfers heat to the environment by conduction, radiation, and
evaporative cooling. Quantifying these effects individually would be challenging
and unnecessary to answer the question as posed.
As a simplification, we can use Newton’s Law of Cooling:
df
= −r ( f − e)
dt
where f is the temperature of the coffee as a function of time and d f /d t is its time
derivative; e is the temperature of the environment (assume 20 ◦ C), which is a
constant in this case, and r is a parameter (also constant) that characterizes the
rate of heat transfer.
It would be easy to estimate r for a given coffee cup by making a few measurements
over time. Let’s assume that that has been done and r has been found to be 0.001
in units of inverse seconds, 1/s.
In what follows, I will give detailed instructions for solving this problem. The
intention of this is to help you build-up your code (incremental development
style) for what may be the most complex problem we have solved so far. What we
ultimately would like to know is if there is an optimal time when the cream should
be added to the coffee to minimize the required cooling time. To answer this, you
should solve for the required cooling time for three cases. First, just let the coffee
(black, no cream) cool to the desired temperature. Second, add the cream at t = 0
and then let the coffee cool. And third, add the cream at the end so that once it
is added, we are at the final, desired temperature. Keep this in mind as you read
through the detailed instructions that follow.
• Using mathematical notation, write the rate function, g , as a function of y,
where y is the temperature of the coffee at a particular point in time.
• Create an M-file named coffee and write a function called coffee that
takes no input variables and returns no output value. Put a simple statement like x=5 in the body of the function and invoke coffee() from the
Command Window .
• Add a function called rate_func that takes t and y and computes g (t , y).
Notice that in this case g does not actually depend on t ; nevertheless, your
function has to take t as the first input argument in order to work with ode45.
Test your function by adding a line like rate_func(0,90) to coffee, then
call coffee from the Command Window .
• Once you get rate_func(0,90) working, modify coffee to use ode45 to
compute the temperature of the coffee (ignoring the cream) for 60 minutes.
342
Chapter 9 Ordinary Differential Equations
Confirm that the coffee cools quickly at first, then more slowly, and reaches
room temperature (approximately) after about an hour.
• Write a function called mix_func that computes the final temperature of a
mixture of two liquids. It should take the volumes and temperatures of the
liquids as parameters.
In general, the final temperature of a mixture depends on the specific heat
of the two substances. But if we make the simplifying assumption that
coffee and cream have the same density and specific heat, then the final
temperature is (v 1 y 1 + v 2 y 2 )/(v 1 + v 2 ), where v 1 and v 2 are the volumes of
the liquids, and y 1 and y 2 are their temperatures.
Add code to coffee to test mix_func.
• Use mix_func and ode45 to compute the time until the coffee is drinkable
if you add the cream immediately.
• Modify coffee so it takes an input variable t that determines how many
seconds the coffee is allowed to cool before adding the cream, and returns
the temperature of the coffee after mixing.
• Use fzero to find the time t that causes the temperature of the coffee after
mixing to be 60 ◦ C.
• What do these results tell you about the answer to the original question?
Is the answer what you expected? What simplifying assumptions does this
answer depend on? Which of them do you think has the biggest effect? Do
you think it is big enough to affect the outcome? Overall, how confident are
you that this model can give a definitive answer to this question? What might
you do to improve it?
Solution:
What a fun problem! The problem statement is very detailed to help you build-up
your code. I will take you through a number of versions of my code as I build it up.
While I will not show any plots, from the rate expression we see that the hotter the
temperature of our beverage, the greater the rate of heat transfer. So when we add
cream we have a step change (decrease) in our temperature, which then results in
a lower rate of heat transfer. Then the question is, is there an optimal time to add
the cream. We will consider three cases. First, we will just let our coffee cool to the
desired temperature. Second, we can add the cream and then let our coffee cool.
And third, we will add the cream at the end so that once it is added, we are at the
final, desired temperature.
The first series of steps are a matter of incremental development steps to get the
code working. I maintained a copy of the function coffee which I used for these
debugging steps. Here is that final version of my debugged code:
9.11 Return of fzero
343
Listing 9.15 coffee.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function res = coffee()
%
%x = 5 % teesting that coffee works
%
%rate_func(0,90) % testing that rate_func works
%
%t0 = 0; % initial time
%tfinal = 60*60; % final time is 60 minutes
%f0 =90; % initial temperature of the coffee
%ode45(@rate_func,[t0,tfinal],f0)
%
mix_func(90,8,20,1) % testing that mix_func works
end
function res = rate_func(t,y)
r = 0.001;% units of 1/s
et = 20;
% room temperature in oC
res = -r*(y-et);
end
function res = mix_func(y1,v1,y2,v2)
res = (y1*v1+y2*v2)/(v1+v2);
end
Before I add cream, first let’s calculate the time required to cool black coffee
(that is, coffee with no cream). I will accomplish this using fzero. To do this,
in coffee_0_fzero the top function is a function with no inputs that simply
calls fzero. Following it we have a new helper function, coffee_0_error_func,
which is our error function to be used with fzero. It takes an argument of time
and returns the temperature of the coffee at that time minus 60. I subtract 60
from the temperature so that when we have our desired temperature of 60 ◦ C, the
function will be equal to (or return a value of) 0. This will allow me to use fzero to
solve.
344
Chapter 9 Ordinary Differential Equations
Listing 9.16 coffee_0_fzero.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
% Case of just cooling coffee (no cream)
%
% For the purpose of this exercise, I will return two optional variables,
% the time when the coffee is safe to drink and the temperature at this
% time. I will make them optional so that if you only wish
% to return time and not the temperature, which you expect to be 60.
%
function [tsafe,ysafe] = coffee_0_fzero()
% Remember, my lower bracket needs to be greater than 0.
% The first thing MATLAB will do when I call fzero with brackets
% is evaluate my function at the end points. 0 is not allowed because
% it would lead to an ode45 call with an intial and final time of
% zero.
tsafe = fzero(@coffee_0_error_func,[0.1,700]);
% Calculating the temperature at this time to see how close fzero
% get's us to the actual value. I will just call our error function,
% adding a value of 60 back to the final result to get the temperature.
ysafe = coffee_0_error_func(tsafe)+60;
end
% Our error function
function res = coffee_0_error_func(t)
% the initial temp of the coffee in oC
y0 = 90;
% Now let's use ode45 to compute the temperature
% of our beverage over a period of time t. Remember the only
% times we know exactly when our function will be evaluate is at
% t0 and tfinal. So if we return the last element of our vector, it will
% be the value of our function at t.
t0 = 0;
tfinal = t;
[T,Y] = ode45(@rate_func,[t0,tfinal],y0);
% We want to choose t such that the final
% temperature is 60 oC. Remember fzero is looking
% for zeroes. Our solution is when we find t such
% that our error function returns a value of 0.
% So we therefore need to subtract 60 from our final
% temperature.
res = Y(end)-60;
end
function res = rate_func(t,y)
r = 0.001;% units of 1/s
et = 20;
% room temperature in oC
res = -r*(y-et);
end
function res = mix_func(y1,v1,y2,v2)
res = (y1*v1+y2*v2)/(v1+v2);
end
9.11 Return of fzero
And now let’s use it:
>> [tsafe,ysafe] = coffee_0_fzero
tsafe = 559.6158
ysafe = 60.0000
Using fzero we obtain a precise answer of 559.6158 s (9.3269 minutes). We can
pass this value to our error function to confirm that the temperature minus 60 is 0
at this time.
In the next iteration of the code, we will add the cream immediately to our coffee.
So effectively by mixing the cream first, we are decreasing our initial temperature
at t = 0. I will again use fzero as before in the updated function coffee_1_fzero.
345
346
Chapter 9 Ordinary Differential Equations
Listing 9.17 coffee_1_fzero.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
% Case of adding cream immediately
%
% For the purpose of this exercise, I will return two optional variables,
% the time when the coffee is safe to drink and the temperature at this
% time, so we can see how close our solution get's us to the desired
% temperature of 60 oC so that we can compare our numerical solution
% strategies. I will make them optional so that if you only wish
% to return time and not the temperature, which you expect to be 60,
% you can.
%
function [tsafe,ysafe] = coffee_1_fzero()
% Remember, my lower bracket needs to be greater than 0.
% The first thing MATLAB will do when I call fzero with brackets
% is evaluate my function at the end points. 0 is not allowed because
% it would lead to an ode45 call with an intial and final time of
% zero.
tsafe = fzero(@coffee_1_error_func,[0.1,700]);
end
% Calculating the temperature at this time to see how close fzero
% get's us to the actual value. I will just call our error function,
% adding a value of 60 back to the final result to get the temperature.
ysafe = coffee_1_error_func(tsafe)+60;
% Our error function
function res = coffee_1_error_func(t)
y1 = 90; % initial temp of coffee in oC
y2 = 20; % initial temp of cream in oC
v1 = 8; % initial volume of coffee in ounces
v2 = 1; % intiial volume of cream in ounces
% Now mixing the coffee and cream and computing
% the initial temp of the mixture
y0 = mix_func(y1,v1,y2,v2);
% Now let's use ode45 to compute the temperature
% of our beverage over a period of 60 minutes.
% I will store the results to a vector that we
% can search through for our solution.
t0 = 0;
tfinal = t;
[T,Y] = ode45(@rate_func,[t0,tfinal],y0);
% We want to choose t such that the final
% temperature is 60 oC. If we use fzero we
% need to look for a value of zero, so
% subtract 60.
res = Y(end)-60;
end
function res = rate_func(t,y)
r = 0.001;% units of 1/s
et = 20;
% room temperature in oC
res = -r*(y-et);
end
9.11 Return of fzero
54 function res = mix_func(y1,v1,y2,v2)
55
res = (y1*v1+y2*v2)/(v1+v2);
56 end
And now let’s use it:
>> [tsafe,ysafe] = coffee_1_fzero
tsafe = 441.8328
ysafe = 60
Using fzero we obtain a precise answer of 441.8328 s (7.3639 minutes). We can
pass this value to the error function to confirm that the temperature minus 60 is 0
at this time.
By adding creamer first our coffee takes 1.9630 minutes less to cool to 60 ◦ C.
From our rate expression, we see that the rate of cooling will be greatest when the
temperature of the coffee is greatest. For the last part then, we ask if we can speed
up the cooling process by first letting the coffee cool for some time t , before we
add the creamer to bring the coffee down to a temperature of 60 ◦ C. For this case
again I will use fzero. The function file will look very much the same as the last
case.
347
348
Chapter 9 Ordinary Differential Equations
Listing 9.18 coffee_2_fzero.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
% Case of adding creat at time t
function [tsafe,ysafe] = coffee_2_fzero()
tsafe = fzero(@coffee_2,[0.1,3600]);
ysafe = coffee_2(tsafe)+60;
end
% Our error function
function res = coffee_2(t)
% Let the coffee (no cream) cool for t
% seconds
y0 = 90;
t0 = 0;
tfinal = t;
[T,Y] = ode45(@rate_func,[t0,tfinal],y0);
%
% Now add the cream to the coffee
y2 = 20; % temperature of the cream
v2 = 1; % volume of the cream in ounces
y1 = Y(end); % temperature ofthe coffee
v1 = 8; % volume of the coffee in ounces
yfinal = mix_func(y1,v1,y2,v2);
%
% Since fzero looks for zeroes, lets shift
% the temperature by 60 C
res = yfinal-60;
end
function res = rate_func(t,y)
r = 0.001;% units of 1/s
et = 20;
% room temperature in oC
res = -r*(y-et);
end
function res = mix_func(y1,v1,y2,v2)
res = (y1*v1+y2*v2)/(v1+v2);
end
>> [tsafe,ysafe] = coffee_2_fzero
tsafe = 441.8328
ysafe = 60
That is exactly how long it took to cool the coffee when we added the creamer first!
The solution is confirmed by calling coffee_2, where we additionally see that
the coffee (without creamer) is first cooled to 65 ◦ C, and then the mixing process
reduces the temperature an additional 5 ◦ C.
So what is going on? From the rate expression, we know that the rate of cooling is
decreasing as the temperature of the coffee decreases (or equivalently as time increases). However, what is more subtle, is the temperature drop upon the addition
of cream is also decreasing as the temperature of the coffee decreases. When the
coffee is at 90, 80, and 70 ◦ C, the decrease in temperature upon adding creamer is
9.12 Isothermal Batch Reactors
7.7778, 6.6667, and 5.5556 ◦ C, respectively.
9.12 Isothermal Batch Reactors
In Section 8.14 we analyzed an isothermal continuous stirred tank reactor (CSTR).
Here we will look at an isothermal batch reactor. Our analysis of isothermal batch
reactors will continue into the next chapter. I will again take this opportunity to
encourage you to look at the free text “A First Course on Kinetics and Reaction
Engineering,” by Carl Lund3 .
9.12.1 Batch Reactor Basics
Just as when “deriving” the design equation for a CSTR, we will again use a mass
balance here to derive the design equation for a batch reactor. For this problem we
will consider an isothermal reactor, so we can solve our mole balance independent
of our energy balance. It is common to look at cases when this is not the case,
which requires solving your mass and energy balance simultaneously. First we
will look at the isothermal case, and then we will add this extra layer later.
Below is an image of a batch reactor which I took from Wikipedia.
Figure 9.10 Cartoon of a batch reactor.
While it probably did not look like this, when you carry out reactions in your
general chemistry and organic chemistry classes, they were carried out in batch.
At t = 0 you charge the reactor with a solution containing your reactants. Then
3 http://wwwresearch.sens.buffalo.edu/karetext/title/title.shtml
349
350
Chapter 9 Ordinary Differential Equations
the reaction proceeds over some period of time. During this time the system
is perfectly mixed, and no additional material is added or removed. For this
problem, we will add (if the reaction is endothermic) or remove (if the reaction is
exothermic) heat at the same rate it is consumed or generated so that the reaction
proceeds isothermally.
Let’s write a mole balance for our system for component A:
INPUT + GENERATION = OUTPUT + ACCUMULATION
In our problem we are not adding or removing anything, so our expression reduces
to:
GENERATION = ACCUMULATION
The accumulation term is something you may have seen in your Mass and Energy
Balance course if you looked at non-steady state balances, and would take the
form here of:
dnA
ACCUMULATION =
dt
It is the time rate of change of the moles of A in the system.
We will assume that the only way moles of A can be generated/consumed is
via a chemical reaction. If we consider here just a single chemical reaction taking
place:
GENERATION = ν A V r
where ν A is the stoichiometic coefficient of A, ν A r is the per unit volume rate of
generation/consumption of A, and V is the volume of the system. With this our
mole balance design equation becomes:
dnA
= νAV r
dt
We will next look at the problem statement for exercise 9.1 that will appear
at the end of the chapter to aid in our discussion of some common notes about
modeling batch reactors. You will then solve exercise 9.1 on your own.
The conversion of A to B (A → B ) takes place in an aqueous solution in an
isothermal batch reactor. The reactor is charged with 1200 L of a 2 M solution of A
at 300 K. (Recall 1 M = 1 mol/L.) Calculate the time required to reach 0.8 fractional
conversion of A. The fractional conversion of A may be computed as
fA =
n 0A − n A
n 0A
where n 0A is the initial number of moles of A (at t = 0) and n A is the number of
moles at a given time.
9.12 Isothermal Batch Reactors
351
The reaction is first order in A and the rate coefficient obeys the Arrhenius
expression with a pre-exponential term equal to 2.4 × 108 s−1 and an activation
energy of 15.3 kcal/mol. Mathematically this means
r = kC A
k = k 0 exp[−E /(RT )]
k 0 = 2.4 × 108 s−1
E = 15.3kcal/mol
9.12.2 Useful Assumption
We have a reaction that takes place in the aqueous phase. The initial concentration of A is rather dilute, 2 moles per liter of water. There are approximately 55.35
moles of water per liter, so this corresponds to a mole fraction of just 0.035. And
since it is a 1 to 1 reaction (for every mole of B created a mole of A is consumed),
the total concentration of A and B will remain the same. Therefore, it is very
reasonable to assume that volume is constant and equal to 1200 L.
9.12.3 Note on Units
I have provided you with the general rate expression. If you want a rate expression
for the consumption of A or generation of B, you multiply by the appropriate
stoichiometric coefficient. Remember stoichiometric coefficients for reactants
are negative (they are being consumed) and positive for reactants. So for the rate
of consumption of A
r A = −kC A
The term C A is a measure of concentration, moles/volume. What units do we
use? Well, the rate is defined as
1 dnA
= r A = −kC A
V dt
The volume, V , is referred to as a normalization factor, and is a very common
choice for liquid phase reactions. It makes the rate an intrinsic property. So the
units of C A just need to agree with n A /V . Note too that since we are assuming V
is constant, we can bring it into the differential and write
1 d n A d (n A /V ) dC A
=
=
V dt
dt
dt
Likewise the fractional conversion can be written in terms of C A by multiplying
the top and bottom of the expression by 1/V to give
fA =
C A0 −C A
C A0
352
Chapter 9 Ordinary Differential Equations
So if you would prefer to use units of concentrations, you can.
The rate coefficient (k) has the same units as the pre-exponential term (k 0 ) of
1/s, the same units as d /d t . So if you solve as is, your unit of time will be seconds.
The term in the exponential, E /(RT ) is dimensionless. You are given E in
kcal/mol. So unless you convert it, you should use R in units of cal/(mol K) and T
in units of K.
9.12.4 Final Note
In this problem we have set everything up to solve using A. As a future note, know
that you do not have to use A if you do not want to. Likewise, we do not need to
solve a differential mole balance for B in order to determine how many moles of
B we have. The moles of A and B are related by stoichiometry. For every mole of A
that is consumed, a mole of B is created in our reaction. Therefore
n 0A − n A = n B − n B0
and since we started with no moles of B
n 0A − n A = n B
C A0 −C A = C B
My very last note is on the rate expression itself. Here we have a relatively
simple expression. This is generally not the case. Depending on the proposed
mechanism or if we have a reversible reaction, the expression is much more
complicated. (No worries, MATLAB does not care!) But the point I would like to
make is that you can’t (and never should be expected to) just look at a reaction and
know what the form of the rate of reaction should be. The only way is to propose
a model (in various ways) and test it by fitting to experimentally generated data.
9.13 Glossary
9.13 Glossary
differential equation (DE): An equation that relates the derivatives of an unknown function.
ordinary DE: A DE in which all derivatives are taken with respect to the same
variable.
partial DE: A DE that includes derivatives with respect to more than one variable
first order (ODE): A DE that includes only first derivatives.
linear: A DE that includes no products or powers of the function and its derivatives.
time step: The interval in time between successive estimates in the numerical
solution of a DE.
first order (numerical method): A method whose error is expected to halve when
the time step is halved.
adaptive: A method that adjusts the time step to control error.
stiffness: A characteristic of some ODEs that makes some ODE solvers run slowly
(or generate bad estimates). Some ODE solvers, like ode23s, are designed
to work on stiff problems.
parameter: A value that appears in a model to quantify some physical aspect of
the scenario being modeled.
353
354
Chapter 9 Ordinary Differential Equations
9.14 Exercises
Exercise 9.1 The conversion of A to B (A → B ) takes place in an aqueous solution in an
isothermal batch reactor. The reactor is charged with 1200 L of a 2 M solution of A at 300
K. (Recall 1 M = 1 mol/L.) Calculate the time required to reach 0.8 fractional conversion
of A. The fractional conversion of A may be computed as
fA =
n 0A − n A
n 0A
where n 0A is the initial number of moles of A (at t = 0) and n A is the number of moles at a
given time.
The reaction is first order in A and the rate coefficient obeys the Arrhenius expression
with a pre-exponential term equal to 2.4×108 s−1 and an activation energy of 15.3 kcal/mol.
Mathematically this means
r = kC A
k = k 0 exp[−E /(RT )]
k 0 = 2.4 × 108 s−1
E = 15.3kcal/mol
Chapter
10
Systems of ODEs
Last chapter we learned how to numerically solve first order initial value ordinary
differential equations. We will build upon this in Chapter 10 and will solve systems
of first order ordinary differential equations. By the end of this chapter you will
be able to:
• Demonstrate the ability to construct vectorized “rate” functions
• Apply ode45 to solve systems of initial value ODEs
• Analyze ecosystem dynamics by setting up and solving intricate physical
models requiring the use of ode45
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
10.1 Lotka-Voltera
The Lotka-Voltera model describes the interactions between two species in an
ecosystem, a predator and its prey. A common example is rabbits and foxes. The
model is governed by the following system of differential equations:
R 0 = aR − bRF
F 0 = ebRF − cF
where
• R is the population of rabbits,
• F is the population of foxes,
• a is the natural growth rate of rabbits in the absence of predation,
355
t Notice here I am using
Lagrange’s notation for
compactness when writing
systems of differential equations; R 0 = ddRt and F 0 = ddFt .
(The notation on the righthand side corresponds to
Leibniz’s notation. Newton’s
dot notation also exists, but
is not as common in the
undergraduate curriculum;
it is used to designate flow
rates, but there is never a
mention of Newton.) Note
that Lagrange’s notation can
also take the form: R t = ddRt
and F t = ddFt .
356
Chapter 10 Systems of ODEs
• c is the natural death rate of foxes in the absence of prey,
• b is the death rate of rabbits per interaction with a fox,
• e is the efficiency of turning eaten rabbits into foxes.
At first glance you might think you could solve these equations by calling
ode45 once to solve for R as a function of time and once to solve for F . The
problem is that each equation involves both variables, which is what makes this a
system of equations and not just a list of unrelated equations. To solve a system,
you have to solve the equations simultaneously.
Fortunately, ode45 can handle systems of equations. The difference is that
the initial condition is a vector that contains initial values R(0) and F (0), and the
output is a matrix that contains one column for R and one for F .
And here’s what the rate function looks like with the parameters a = 0.1,
b = 0.01, c = 0.1 and e = 0.2:
Listing 10.1 lotka.m
1
2
3
4
function res = lotka(t, V)
% unpack the elements of V
r = V(1);
f = V(2);
5
%
a
b
c
e
6
7
8
9
10
set the parameters
= 0.1;
= 0.01;
= 0.1;
= 0.2;
11
% compute the derivatives
drdt = a*r - b*r*f;
dfdt = e*b*r*f - c*f;
12
13
14
15
16
17
18
end
% pack the derivatives into a vector
res = [drdt; dfdt];
As usual, the first input variable is time. The second input variable is a vector
with two elements, R(t ) and F (t ). I gave it a capital letter to remind me that it is a
vector. The body of the function includes four paragraphs, each explained by a
comment.
The first paragraph unpacks the vector by copying the elements into scalar
variables. This isn’t necessary, but giving names to these values helps me remember what’s what. It also makes the third paragraph, where we compute the
10.1 Lotka-Voltera
derivatives, resemble the mathematical equations we were given, which helps
prevent errors.
The second paragraph sets the parameters that describe the reproductive
rates of rabbits and foxes, and the characteristics of their interactions. If we
were studying a real system, these values would come from observations of real
animals, but for this example I chose values that yield interesting results.
The last paragraph packs the computed derivatives back into a vector. When
ode45 calls this function, it provides a vector as input and expects to get a vector
as output.
Sharp-eyed readers will notice something different about this line:
res = [drdt; dfdt];
The semi-colon between the elements of the vector is not an error. It is necessary
in this case because ode45 requires the result of this function to be a column
vector.
Now we can run ode45 like this:
>> ode45(@lotka, [0, 365], [100, 10])
>> xlabel('Time in days')
>> ylabel('Population')
As always, the first argument is a function handle, the second is the time interval,
and the third is the initial condition. The initial condition is a vector: the first
element is the number of rabbits at t = 0, the second element is the number of
foxes.
The order of these elements (rabbits and foxes) is up to you, but you have
to be consistent. That is, the initial conditions you provide when you call ode45
have to be the same as the order, inside lotka, where you unpack the input vector
and repack the output vector. MATLAB doesn’t know what these values mean; it
is up to you as the programmer to keep track.
But if you get the order right, you should see something like this:
357
t As we have seen previously, we could alternatively create a general rate
function M-file which
takes the parameters as
inputs, and then define an
anonymous function in the
Command Window in which
we pass this information.
358
Chapter 10 Systems of ODEs
120
100
Population
80
60
40
20
0
0
50
100
150
200
250
300
350
400
Time in days
Figure 10.1 ode45(@lotka,[0,365],[100,10])
The x-axis is time in days; the y-axis is population. The top curve shows the
population of rabbits; the bottom curve shows foxes. This result is one of several
patterns this system can fall into, depending on the starting conditions and the
parameters. As an exercise, try experimenting with different values.
10.2 What can go wrong?
The output vector from the rate function has to be a column vector. If I modify
Listing 10.1 (lotka) to change
res = [drdt; dfdt];
to
res = [drdt, dfdt];
I get the following error:
>> ode45(@lotka, [0, 365], [100, 10])
Error using odearguments (line 90)
LOTKA must return a column vector.
Error in ode45 (line 113)
[neq, tspan, ntspan, next, t0, tfinal, tdir, y0, f0, odeArgs,
odeFcn, ...
10.3 Output matrices
Which is pretty good as error messages go. It’s not clear to me why it needs to be a
column vector, but that’s not our problem.
Another possible error is reversing the order of the elements in the initial
conditions, or in the vectors inside lotka. Again, MATLAB doesn’t know what
the elements are supposed to mean, so it can’t catch errors like this; it will just
produce incorrect results.
10.3 Output matrices
As we saw before, if you call ode45 without assigning the results to variables, it
plots the results. If you assign the results to variables, it suppresses the figure.
Here’s what that looks like:
>> [T, M] = ode45(@lotka, [0, 365], [100, 10]);
As in previous examples, T is a vector of time values where ode45 made estimates
of the variables. But unlike previous examples, the second output variable is a
matrix containing one column for each variable (in this case, R and F ) and one
row for each time value.
>> size(M)
ans = 185
2
This structure—one column per variable—is a common way to use matrices.
plot understands this structure, so if you do this:
>> plot(T, M)
MATLAB understands that it should plot each column from M versus T.
You can also copy the columns of M into other variables like this:
>> R = M(:, 1);
>> F = M(:, 2);
In this context, the colon represents the range from 1 to end, so M(:, 1) means
“all the rows, column 1” and M(:, 2) means “all the rows, column 2.”
>> size(R)
ans = 185
1
>> size(F)
ans = 185
1
So R and F are column vectors.
359
360
Chapter 10 Systems of ODEs
If you plot these vectors against each other, like this
>> plot(R, F)
You get a phase plot that looks like this:
22
20
18
16
14
12
10
8
6
4
20
30
40
50
60
70
80
90
100
110
Figure 10.2 plot(R,F)
Each point on this plot represents a certain number of rabbits (on the x-axis) and
a certain number of foxes (on the y-axis).
Since these are the only two variables in the system, each point in this plane
describes the complete state of the system. Over time, the state moves around
the plane; this figure shows the path traced by the state during the time interval.
This path is called a trajectory. Since the behavior of this system is periodic, the
resulting trajectory is a loop.
If there are 3 variables in the system, we need 3 dimensions to show the
state of the system, so the trajectory is a 3-D curve. You can use plot3 to trace
trajectories in 3 dimensions, but for 4 or more variables, you are on your own.
10.4 Examples
The best way to get comfortable using ode45 is to solve some problems. And the
best problems to practice on are ones that you know the answer to. In addition
to the examples here, for more practice I would encourage you to solve some of
the example problems with analytical solutions provided in “Notes on Diffy Qs:
Differential Equations for Engineers”.
10.4 Examples
361
Example 10.1
(Link to screen cast with accompanying M-file.)
Let’s solve the following system of differential equations
x 0 = −y
y 0 = (1.01)x − (0.2)y
subject to the initial conditions
x(0) = 0
y(0) = −1
Let’s start by solving this problem analytically, because we can, and hopefully it will help you appreciate how awesome numerically solving using
MATLAB is.
We start by noticing
£
¤
x 00 = −y 0 = (1.01)x − (0.2)y = (−1.01)x − (0.2)x 0
We now have a single linear second-order differential equation to solve
x 00 = (−1.01)x − (0.2)x 0
Since it is second-order, we need two initial conditions. No problem, we
have them
x(0) = 0
0
x (0) = −y(0) = 1
To solve, we start by writing the characteristic equation
r 2 + (0.2)r + 1.01 = 0
We can factor and solve
r 2 + (0.2)r + 1.01 = (r + 0.1)2 + 1 = 0
(r + 0.1)2 = −1
p
r = −0.1 ± ( − 1) = −0.1 ± i
362
Chapter 10 Systems of ODEs
This leads to the general solution
x(t ) = e −t /10 (A cos t + B sin t )
Applying our first initial condition
x(0) = A = 0
Leaving us with
x(t ) = B e −t /10 sin t
Before we can apply our second initial condition, we need to differentiate x
with respect to t . Applying the product rule
x0 = −
1
B e −t /10 sin t + B e −t /10 cos t
10
Now applying our second initial condition
x 0 (0) = B = 1
Leading to the final solution, where recall y = −x 0
x(t ) = e −t /10 sin t
1
y(t ) = e −t /10 (sin t − 10 cos t )
10
Now let’s solve numerically using MATLAB. Let’s start by writing a function
to compute the rate of change of x and y with respect to t (i.e., x 0 and y 0 ).
Listing 10.2 example_10_1.m
1
2
3
4
function res = example_10_1(t, V)
% unpack the elements of V
x = V(1);
y = V(2);
5
% compute the derivatives
dxdt = -y;
dydt = 1.01*x-0.2*y;
6
7
8
9
10
11
12
end
% pack the derivatives into a vector
res = [dxdt; dydt];
10.4 Examples
363
We will solve subject to the initial conditions x(0) = 0 and y(0) = −1, and
let’s solve over the range 0 < t < 50. Note that the way I set-up the function
the first equation is our equation for x and the second is our equation for y.
Let’s solve! I will solve by storing the solution to vector T (time) and matrix
M (first column will be the solution for x and the second column will be the
solution for y). I will plot the solution, compare to our analytic result, and
then plot the phase plot.
>>
>>
>>
>>
>>
>>
>>
>>
>>
[T,M] = ode45(@example_10_1,[0,50],[0,-1]);
X = M(:,1);
Y = M(:,2);
hold on
plot(T,X,'-b',T,Y,'-r')
xlabel('t')
ylabel('x or y')
title('Solution to Example 10.1')
legend('x','y')
Here is the resulting plot
Solution to Example 10.1
1
x
y
0.8
0.6
0.4
x or y
0.2
0
-0.2
-0.4
-0.6
-0.8
-1
0
10
20
30
40
50
t
Figure 10.3 ode45(@example_10_1,[0,50],[0,-1])
Let’s compare to our analytic solution. Keeping the plot open and then
>> Ta = linspace(0,50);
>> Xa = exp(-Ta./10).*sin(Ta);
>> Ya = (1/10)*exp(-Ta./10).*(sin(Ta)-10*cos(Ta));
364
Chapter 10 Systems of ODEs
>> plot(Ta,Xa,'bo',Ta,Ya,'ro')
>> legend('x','y','x ref','y ref')
This results in the following plot, where the analytic solution for x is drawn
as blue circles and the analytic solution for y as red circles
Solution to Example 10.1
1
x
y
x ref
y ref
0.8
0.6
0.4
x or y
0.2
0
-0.2
-0.4
-0.6
-0.8
-1
0
10
20
30
40
50
t
Figure 10.4 Comparing our analytic and numeric solutions.
A perfect match!
Now let’s close the plot and plot our phase plot. We will use our numeric
solution.
>> plot(X,Y,'-k')
>> xlabel('x')
>> ylabel('y')
And here is the result:
10.4 Examples
365
0.8
0.6
0.4
0.2
y
0
-0.2
-0.4
-0.6
-0.8
-1
-0.8
-0.6
-0.4
-0.2
0
0.2
0.4
0.6
0.8
1
x
Figure 10.5 Our phase plot for Example 10.1
Cool! A spiral trajectory.
Example 10.2
(Link to screen cast with accompanying M-file.)
Solve the following system of differential equations
x 0 = 4x − 3y
y 0 = 6x − 7y
subject to the initial conditions
x(0) = 2
y(0) = −1
We wont work through the analytic solution to this one, but if you would
like to compare the final answer is
x(t ) = 3e 2t − e −5t
y(t ) = 2e 2t − 3e −5t
366
Chapter 10 Systems of ODEs
Now let’s solve numerically using MATLAB. Let’s start by writing a function
to compute the rate of change of x and y with respect to t (i.e., x 0 and y 0 ).
Listing 10.3 example_10_2.m
1
2
3
4
function res = example_10_2(t, V)
% unpack the elements of V
x = V(1);
y = V(2);
5
% compute the derivatives
dxdt = 4*x-3*y;
dydt = 6*x-7*y;
6
7
8
9
10
11
12
end
% pack the derivatives into a vector
res = [dxdt; dydt];
We will solve subject to the initial conditions x(0) = 2 and y(0) = −1, and
let’s solve over the range 0 < t < 1. Note that the way I set-up the function
the first equation is our equation for x and the second is our equation for y.
Let’s solve! I will solve by storing the solution to vector T (time) and matrix
M (first column will be the solution for x and the second column will be the
solution for y). I will plot the solution and then plot the phase plot.
>>
>>
>>
>>
>>
>>
[T,M] = ode45(@example_10_2,[0,1],[2,-1]);
plot(T,M)
xlabel('t')
ylabel('x or y')
title('Solution to Example 10.2')
legend('x','y')
Here is the resulting plot
10.4 Examples
367
Solution to Example 10.2
25
x
y
20
x or y
15
10
5
0
-5
0
0.2
0.4
0.6
0.8
t
Figure 10.6 ode45(@example_10_2,[0,1],[2,-1])
And comparing to the analytic solutions:
>>
>>
>>
>>
>>
>>
Ta = linspace(0,1);
Xa = 3*exp(2*Ta)-exp(-5*Ta);
Ya = 2*exp(2*Ta)-3*exp(-5*Ta);
hold on
plot(Ta,Xa,'bo',Ta,Ya,'ro')
legend('x','y','x ref','y ref')
1
368
Chapter 10 Systems of ODEs
Solution to Example 10.2
25
x
y
x ref
y ref
20
x or y
15
10
5
0
-5
0
0.2
0.4
0.6
0.8
1
t
Figure 10.7 Comparing our analytic and numerical solutions.
Now let’s close the plot and plot our phase plot. Or to plot in a separate,
new figure window, begin with the command figure(2).
>>
>>
>>
>>
>>
>>
figure(2)
X = M(:,1);
Y = M(:,2);
plot(X,Y)
xlabel('x')
ylabel('y')
And here is the result:
10.4 Examples
369
16
14
12
10
y
8
6
4
2
0
-2
0
5
10
15
20
25
x
Figure 10.8 Our phase plot for Example 10.2
Notice that when you typed figure(2), “Figure 2” became active. If you
wanted to return to “Figure 1” and update it, you would switch back to
“Figure 1” as the active figure using the command figure(1). And then if
you wanted to return back to “Figure 2”, use figure(2).
Example 10.3
(Link to screen cast with accompanying M-files.)
According to Wikipedia, “The Lorenz attractor, introduced by Edward Lorenz in
1963, is a non-linear three-dimensional deterministic dynamical system derived
from the simplified equations of convection rolls arising in the dynamical equations of the atmosphere. For a certain set of parameters the system exhibits chaotic
behavior and displays what is today called a strange attractor...”
The system is described by this system of differential equations:
x 0 = σ(y − x)
y 0 = x(r − z) − y
z 0 = x y − bz
Common values for the parameters are σ = 10, b = 8/3 and r = 28.
Use ode45 to estimate a solution to this system of equations.
370
Chapter 10 Systems of ODEs
(a) The first step is to write a function named lorenz that takes t and V as input
variables, where the components of V are understood to be the current values
of x, y and z. It should compute the corresponding derivatives and return
them in a single column vector.
(b) The next step is to test your function by calling it from the command line
with values like t = 0, x = 1, y = 2 and z = 3? Once you get your function
working, you should make it a silent function before calling ode45.
(c) Assuming that Step 2 works, you can use ode45 to estimate the solution for
the time interval t 0 = 0, t e = 30 with the initial condition x = 1, y = 2 and
z = 3.
(d) Use plot3 to plot the trajectory of x, y and z.
Solution:
Let’s start by writing our rate of change function, here called lorenz.
Listing 10.4 lorenz.m
1 function res = lorenz (t,V)
2
% Unpact vector V
3
x = V(1);
4
y = V(2);
5
z = V(3);
6
7
% Parameter list
8
sigma = 10;
9
b = 8/3;
10
r = 28;
11
12
% Compute the rates of change
13
dxdt = sigma*(y-x);
14
dydt = x*(r-z)-y;
15
dzdt = x*y-b*z;
16
17
% Return the rates as a column vector
18
res = [dxdt; dydt; dzdt];
19 end
Before moving on, we can test its use with t = 0, x = 1, y = 2, and z = 3. Remember
that lorenz only takes two arguments, so the values of the function will need to
be passed as a single vector. (You can use either column or row since a single index
is needed in either case.)
>> lorenz(0,[1;2;3])
ans =
10
23
-6
10.4 Examples
371
Now let’s solve the system of ODEs over the time interval t 0 = 0 to t e = 30 subject
to the initial conditions x(0) = 1, y(0) = 2, and z(0) = 3. I will store the results to a
vector and matrix. I will then separate the matrix that contains the values of the
functions x, y and z as a function of time into separate column vectors, which I
can use to make a plot of our solution and of our phase plot.
>>
>>
>>
>>
>>
>>
>>
>>
[T,M]= ode45(@lorenz,[0,30],[1,2,3]);
X = M(:,1);
Y= M(:,2);
Z = M(:,3);
plot(T,X,'r-',T,Y,'b-',T,Z,'g-')
xlabel('t')
ylabel('x, y, or z')
legend('x','y','z')
And here is the resulting plot:
50
x
y
z
40
30
x, y, or z
20
10
0
-10
-20
-30
0
5
10
15
20
25
t
Figure 10.9 ode45(@lorenz,[0,30],[1,2,3])
And now for the phase plot:
>>
>>
>>
>>
plot3(X,Y,Z,'b-')
xlabel('x')
ylabel('y')
zlabel('z')
With the resulting plot:
30
372
Chapter 10 Systems of ODEs
50
40
z
30
20
10
0
50
20
10
0
0
-10
y
-50
-20
x
Figure 10.10 Lorenz phase plot
In Spring 2018 I had a student discover on accident that MATLAB actually has a
built-in lorenz function. The built-in function is actually a MATLAB GUI, that
solves this exact problem. You need just call the function lorenz without any
inputs. There is actually a lotka function too, but MATLAB does not provide
enough information with regards to how to run it or what exactly it does.
10.5 Euler returns!
In Chapter 9 we used Euler’s method to solve first order ordinary differential
equations (ODEs). We can use Euler’s equation here too for systems of ODEs. The
key is that given values of our functions at t 0 , we can propagate forward in time
to t 0 + ∆t .
You will notice the function is written using vectors and matrices. While to
someone new to MATLAB this may appear less readable, this makes the code
highly generalized. It can be used to solve a system of an arbitrary number of
equations. Without further delay, here it is:
10.5 Euler returns!
373
Listing 10.5 euler_ode_system.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
% function [T,Y] = euler_ode_system(fcn_handle, Trange, dt, IC)
% Function to use Euler's method to solve a system of
% differential equations.
%
% Required input:
% fcn_handle: The function handle to the function to
%
compute the rate of change of our
%
functions. Rates can be returned as
%
either a column or row vector.
% Trange: Time vector where Trange = [t0, tfinal]
% dt: the time step
% IC: Intial condition vector. If we have functions
%
X and Y, this is IC = [X0, Y0]. However, it
%
is designed for an arbitrary number of
%
of equations. Note that I assume it is a row vector.
%
The effects my assigment of the initial conditions to
%
the first row of the Y matrix.
%
function [T,Y] = euler_ode_system(fcn_handle, Trange, dt, IC)
%
% Un-packing Trange. The first element is t0 and the second is Tfinal
t0 = Trange(1);
tfinal = Trange(2);
%
%
%
T
T
With a fixed timestep, we can create a vector of times at which we
will evaluate our function. I will then take the transpose to get a
column vector like ode45.
= t0:dt:tfinal;
= T';
% Pre-size our Y matrix. It will be setup like a matrix just like ode45
% would use. The number of rows will be the same as the length of T.
% The number of columns will be the same as the number of functions,
% which is the length of IC
Y = zeros(length(T),length(IC));
%
% Store the initial conditions row 1 of Y.
Y(1,:) = IC;
% Looping over all times. We will start with i=2 since i=1 just
% corresponds to our initial conditions. I could start at i=1, but then
% need to shift my indices.
for i=2:length(T)
DY = fcn_handle(T(i-1),Y(i-1,:));
% Note I need to transpose DY. It is a column vector as returned by
% the rate function. I will add to it the i-1 row of my Y vector,
% which is row vector.
Y(i,:) = DY'*dt+Y(i-1,:);
end
end
374
Chapter 10 Systems of ODEs
Using Euler’s method to solve our Lotka-Voltera model example with a step
size of 0.1 days:
>> [T,M]=euler_ode_system(@lotka,[0,365],0.1,[100,10]);
>> plot(T,M)
120
100
80
60
40
20
0
0
50
100
150
200
250
300
350
400
Figure 10.11 Using Euler’s method to solve our Lotka-Voltera model example.
Which appears to be in good agreement with ode45. Nice! But to be sure, let’s
compare to our results using ode45. The peaks seem to be growing, which isn’t
exactly right.
>> hold on
>> [Tr,Mr]=ode45(@lotka,[0,365],[100,10]);
>> plot(Tr,Mr(:,1),'bo',Tr,Mr(:,2),'ro')
10.6 Isothermal Batch Reactors: Multiple Reactions
375
120
100
80
60
40
20
0
0
50
100
150
200
250
300
350
400
Figure 10.12 Comparing Euler to ode45 with a step size of 0.1 days.
Oh no! The results do differ and appear to get worse with time. Remember, the
errors in the calculation get propagated with time as each step is dependent on
the previous. Let’s try a smaller timestep.
>> [T2,M2]=euler_ode_system(@lotka,[0,365],0.01,[100,10]);
>> plot(T2,M2,'-k')
Perfect!
Now that we have spent some time working with Euler’s method, have you
thought of any ways to improve it? The main limitation is that we evaluate the rate
of change at t 0 , and assume that it is constant over the range t 0 to t 0 + ∆t . This
is correct in the limit that ∆t → 0. (Put differently, we are assuming the function
is linear over this range.) One scheme to improve Euler’s method is to first use
Euler’s method to estimate the value of the function and it’s derivative forward in
time. Then, return to t 0 (or an earlier time) and use these predictions to improve
our estimate of the rate of the function. While I certainly do not give it justice here,
this is the general idea behind the predictor-corrector scheme of numerically
solving ODEs.
10.6 Isothermal Batch Reactors: Multiple Reactions
In Section 8.14 we analyzed an isothermal continuous stirred tank reactor (CSTR),
and then in exercise 8.7 you analyzed parallel and series reactions in an isothermal
CSTR. In Section 9.12 we analayzed an isothermal batch reactor with a single
reaction. Here will build upon this and consider parallel and series reactions in
376
Chapter 10 Systems of ODEs
an isothermal batch reactor. We will discuss in detail the system here (which is
very similar to exercise 8.7, and then you will perform the analysis on your own
for exercise 10.7.
Okay, so now that we have a grasp of batch reactor basics, let’s consider the
case of multiple reactions. We will have two scenarios. First we will have the case
of parallel reactions. For this case, the reactant (or reactants) is consumed by two
or more different reactions. For our exercise we will consider the case:
A→B
(P.1)
A →C
(P.2)
The second case will be series reactions, in which the reactant (or reactants) form
an intermediate product which reacts further to form another product. For our
exercise we will consider the case:
A→B
(S.1)
B →C
(S.2)
When labeling the equations I use the pre-fix “P” and “S” just to remind myself
that the equations correspond to parallel and series reactions, receptively. In our
expressions I will just use the number of the equation.
In both cases, B will be our desired product and C will be an undesired product.
When quantifying reaction progress, we define the selectivity of B relative to C (or
desired product relative to undesired product) as
S B /C =
nB
nC
How does our mole balance design equation change? Well, before when we
had a single reaction we had
dnA
= νAV r
dt
Now, we just need to sum over each reaction. We will use a subscript 1 to correspond to reaction 1 and a subscript 2 to correspond to reaction 2. (Note that both
the rate r and stoichiometic coefficient ν A will depend on the reaction number.
Also note that it is possible to have a stoichiometric coefficient of 0.) This gives
the net rate of reaction for the parallel case:
dnA
= ν A,1V r 1 + ν A,2V r 2
dt
For this problem, we need to know both n B and nC . Remember for a single
reaction the number of moles of each species is related by stoichiometry. This will
not be the case here with our multiple reaction. We will therefore have a system of
ODEs to solve; a mole balance design equation for component B and C, and since
10.6 Isothermal Batch Reactors: Multiple Reactions
our rate expression is dependent on A, solve for A too. For the parallel reaction
case we have:
dnA
= ν A,1V r 1 + ν A,2V r 2
dt
d nB
= νB,1V r 1 + νB,2V r 2
dt
d nC
= νC ,1V r 1 + νC ,2V r 2
dt
And for the series reaction case we have the same set of equations. What will
change is the numerical values of the stoichiometric coefficients and the expression for r 2 .
dnA
= ν A,1V r 1 + ν A,2V r 2
dt
d nB
= νB,1V r 1 + νB,2V r 2
dt
d nC
= νC ,1V r 1 + νC ,2V r 2
dt
Since we are starting with just A, our initial conditions will be:
n 0A = 2400
n B0 = 0
nC0 = 0
For reaction 1 (both parallel and series), use the same parameters as before,
namely,
r 1 = k 1C A
k 1 = k 0,1 exp[−E 1 /(RT )]
k 0,1 = 2.4 × 108 s−1
E 1 = 15.3kcal/mol
For reaction 2 for the parallel reactions (P.2),
r 2 = k 2C A
k 2 = k 0,2 exp[−E 2 /(RT )]
E 2 = 15.3kcal/mol
And for reaction 2 for the series reactions (S.2),
r 2 = k 2C B
k 2 = k 0,2 exp[−E 2 /(RT )]
E 2 = 15.3kcal/mol
377
378
Chapter 10 Systems of ODEs
10.7 Glossary
state: If a system can be described by a set of variables, the values of those
variables are called the state of the system.
phase plot: A plot that shows the state of a system as point in the space of possible states.
trajectory: A path in a phase plot that shows how the state of a system changes
over time.
10.8 Exercises
The first 5 questions are actually one long exercise. However, I have broken it up
into small parts in hopes of guiding you to the final solution. As I have mentioned
previously, I view MATLAB as powerful educational tool. We can set-up a model
for a physical system of interest, and then perform virtual experiments looking at
the effect of perturbing variables. And since we are not concerned about solving
problems analytically, we can hypothesize about how to make our model more
realistic, and then readily test our hypothesis.
Exercise 10.1 We started the chapter by considering a Lotka-Voltera predator-prey model.
The Lotka-Voltera model describes the interactions between two species in an ecosystem,
a predator and its prey. A common example is rabbits and foxes. The model is governed
by the following system of differential equations:
R 0 = aR − bRF
F 0 = ebRF − cF
where
• R is the population or rabbits
• F is the population of foxes
• a is the natural growth rate of rabbits in the absence of predation
• c is the natural death rate of foxes in the absence of prey
• b is the death rate of rabbits per interaction with a fox
• e is the efficiency of turning eaten rabbits into foxes
Let’s begin by making sure we have our model set-up correctly. Use the following values
of the parameters: a = 0.1, b = 0.01, c = 0.1, and e = 0.2. Also, assume we initially have
100 rabbits and 10 foxes (R(0) = 100 and F (0) = 10). Solve for the population of rabbits
over the course of 365 days. (Note that day is the units of time used in the model.) While
in reality we can not have a fractional number of rabbits and foxes, on day 365 I find the
model predicts we have 22.6552 rabbits and 6.7519 foxes. Use these numbers to confirm
your code is working correctly before moving on.
10.8 Exercises
379
Exercise 10.2 Plotting the rabbit and fox population over the period of 365 days, we
find they exhibit oscillatory behavior. With this set of parameters, the maximum rabbit
population is the same as the initial population. You will notice that in our model (a) the
growth rate of rabbits in the absence of predation and (c) the death rate of foxes in the
absence of prey are equivalent. What happens if the growth rate of rabbits were to double,
a = 0.2, keeping everything else the same? Plot your results. Comment on the how the
maximum value of the rabbit and fox population change, and also on how the oscillatory
behavior changes. Does this make physical sense?
Restore a back to its original value of a = 0.1. Now modify the death rate of foxes, c. If
we make c larger, then foxes have a shorter life expectancy. And if we make c smaller, then
the foxes live longer. What is the effect of changing c? Does this make physical sense?
Exercise 10.3 A limitation of the Lotka-Voltera model is the rabbit growth term in the
absence of predation. Specifically, if there are no foxes, then
R 0 = aR
Again using a = 0.1 and an initial rabbit population of 100, solve for the population of
rabbits over the course of 365 days.
You will find this corresponds to an exponential growth of rabbits, and after 365
days we have 7.1095 × 1017 rabbits. Wow! The predicted behavior is not physical. While
the population may grow exponentially initially, the population must be restricted by
resource availability.
Exercise 10.4 One way to fix the unreasonable exponential growth of rabbits is to instead
use a two-term logistic growth model of the form
R 0 = aR −
a 2
R
k
where k is the carrying capacity of the system. That is, the maximum number of rabbits
that the system can support. Again use a = 0.1 and an initial rabbit population of 100.
Try using k = 150 (a carrying capacity greater than the initial population) and k = 50 (a
carrying capacity less than the initial population). Solve for the population of rabbits over
the course of 365 days. What do the results look like?
Does this model seem more appropriate to you?
Exercise 10.5 Let’s return to our original system of equations, but now let’s replace the
exponential growth term with the two-term logistic growth model:
³
a ´
R 0 = aR − R 2 − bRF
k
F 0 = ebRF − cF
Let’s continue to use the same set of parameters: a = 0.1, b = 0.01, c = 0.1, and e = 0.2.
Also, assume again we initially have 100 rabbits and 10 foxes (R(0) = 100 and F (0) = 10).
Let’s look at the effect of the carrying capacity on the system. In the introduction and
Problem 1, we were effectively looking at the case of k → ∞. For that case we observed
oscillatory behavior. Now use k = 150. Solve for the population of rabbits over the course
of 365 days. Plot the results. How does the behavior of the system change? As a reference
to make sure your code is working correctly, at 365 days I find the population of rabbits
and foxes to be 50.0189 and 6.6493, respectively. To further understand what is going on,
solve for 730 days (2 years) or maybe even 10 years. How cool!
380
Chapter 10 Systems of ODEs
What I find interesting is the effect of the carrying capacity on the long-time population of the system. By long-time, let’s solve for a period of 3,650 days (10 years). Try using
value of k of 50, 100, 200, 300, 400, 1000. Plot the population and comment on how the
population after 10 years changes with k.
Exercise 10.6 Compartmental models simplify the mathematical modelling of infectious
diseases. Here we will consider the simplified SIR model wherein the population is
assigned with labels Susceptible, Infectious, or Recovered. The SIR model may be used
to model the dynamics of an epidemic such as the flu or more recently COVID-19, which
was crucial for guiding public policy decisions. If you are interested in more details,
please have a look at the report “Modeling epidemics with differential equations.” See
also the excellent (and free during the pandemic) article “Use of a Modified SIRD Model
to Analyze COVID-19 Data.” In that work the authors improve upon the SIR model similar
to how we improved upon the standard Lotka-Voltera model. Here we will stick with
the standard SIR model for simplicity, but you can experiment for fun after the course is
complete.
SARS-COV-2 is the virus that causes COVID-19. How does SARS-COV-2 spread? From
the news we have learned that SARS-COV-2 spreads between people in close contact
(the reason for social distancing), and through respiratory droplets produced when an
infected person coughs, sneezes, or talks (the reason for wearing masks). To model the
spread, We will apply three labels to the population.
1. S- people are healthy but susceptible to getting infected
2. I- people are infected
3. R- people recover
We will assume that:
1. At any time the total population is conserved (i.e., everyone recovers and no one
dies in our model)
2. Susceptible people get sick by interacting with infected people
The resulting SIR model takes the form:
S 0 = −r SI
0
I = r SI − γI
R 0 = γI
where
• S is the fraction of the population that is susceptible,
• I is the fraction of the population that is infectious,
• R is the fraction of the population that is recovered,
• r is the rate constant representative of how quickly people are leaving the susceptible group and getting infected,
• γ is the rate constant representative of how quickly infected people are recovering.
10.8 Exercises
381
Initially, at t = 0, assume R = 0, I = 1×10−6 , and R + I +S = 1. Assuming a value of r = 0.05
and γ = 0.03, solve for the value of S, I , and R over the range t = 0 to 1000 days.
The goal of social distancing and wearing masks is to decrease the value of r . What
happens as r decreases?
By developing treatments for COVID-19, such as antivirals, the goal is to decrease the
value of γ. What happens as γ decreases?
Exercise 10.7 For the parallel and series reactions described in section 10.6, I would like
you to plot the composition of each species versus time and the selectivity of B relative to
C versus time. For the parallel case, I would like you to consider: k 0,2 = 0.1k 0,1 , k 0,2 = k 0,1 ,
and k 0,2 = 10k 0,1 . For the series case I would like you to consider: k 0,2 = 0.1k 0,1 , k 0,2 = k 0,1 ,
and k 0,2 = 10k 0,1 .
Compare the behavior of parallel and series reactions, and discuss how you might
“optimize” your reactor in both cases. Please summarize your findings in a short report
supported with plots. How do your results compare to that of a CSTR operated with the
same space time? (That is, compare to your results from Exercise 8.7.)
Note: at t = 0, n B = CC = 0. You should therefore only plot the selectivity of B relative
to C versus time for t > 0.
Exercise 10.8 In the last exercise, we reconsidered our systems of reactions from Exercise 8.7, but now used a batch reactor instead of a CSTR. Here we will do the same and
revisit our system of reactions from Exercise 8.2, but again using a batch reactor instead
of a CSTR.
Reactants A and B can react irreversibly to produce either a desired product, D, or an
undesired product, U, as shown in the equations below:
A +B → D
(1)
A +B →U
(2)
The corresponding rate expressions are given by:
½
¾
¡
¢
−15300J/mol
r 1 = 1.12 × 102 min−1 exp
CA
RT
¾
½
¡
¢
−23700J/mol
2
−1
CB
r 2 = 1.87 × 10 min exp
RT
At t = 0 a 25 gallon batch reactor is charged with a liquid solution containing 10 mol A
per gallon and 12 mol B per gallon at 350 K. The batch reactor is operated isothermally
for 2 minutes.
What is the conversion of the limiting reagent? What is the selectivity (in mol D per
mol U)? The limiting reagent will be the reactant that will be consumed first. Here, for
each mol of A consumed a mol of B is consumed. The limiting reagent will therefore be
the species with the smallest concentration in the feed. Why would the conversion with
respect the limiting reagent be preferred?
Again, compare your results to Exercise 8.2, where the CSTR was operated for an
equivalent space time.
11
Chapter
Second-order systems
Last chapter we learned how to numerically solve systems of first order initial
value ordinary differential equations. We will build upon this in Chapter 11 and
will solve higher order differential equations. The key will be to re-write our higher
order differential equation as a series of first order differential equations. By the
end of this chapter you will be able to:
• Explain how to take higher order ODEs and re-write them as a system of
first order ODEs
• Apply ode45 to model projectile trajectories
• Demonstrate ability to update ode45 call to specify an “Event”
• Analyze the role of drag on projectile trajectories
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
11.1 Nested functions
In the Section 6.1 (on page 173), in listing 6.1 we saw an example of an M-file with
more than one function. I will copy listing 6.1 here but will remove the comments
to save space.
383
384
Chapter 11 Second-order systems
t When we first discussed
functions, I recommended
the MATLAB documentation page “Share Data
Between Workspaces.” The
best practice for sharing
data is by passing arguments, which includes the
use of anonymous functions. GNU Octave makes
a similar recommendation.
Now that we have extensive experience working
with functions, I introduce
nested functions, which
once they understand how
they work, many students
find to be a very convenient
alternative to using anonymous functions.
Listing 11.1 v_virial.m
1
2
3
4
function res = v_virial( t,p_bar,tc,pc_bar,omega )
p = p_bar*1e5;
pc = pc_bar*1e5;
r = 8.314;
5
tr = t/tc;
6
7
b0 = 0.083-0.422/(tr^(1.6));
b1 = 0.139-0.172/(tr^(4.2));
b = r*tc/pc*(b0+omega*b1);
8
9
10
11
z = 1 + b*p/(r*t);
12
13
vig = v_ideal_gas(t,p_bar);
14
15
16
17
end
res = z*vig;
18
19
20
21
22
23
24
function res = v_ideal_gas( t,p_bar )
p = p_bar*1e5;
r = 8.314;
v = r*t/p;
res = v*(100^(3));
end
Because the first function ends before the second begins, they are at the same
level of indentation. Functions like these are parallel, as opposed to nested. A
nested function is defined inside another, like this:
11.1 Nested functions
385
Listing 11.2 v_virial_2.m
1
2
3
4
function res = v_virial_2( t,p_bar,tc,pc_bar,omega )
p = p_bar*1e5;
pc = pc_bar*1e5;
r = 8.314;
5
tr = t/tc;
6
7
b0 = 0.083-0.422/(tr^(1.6));
b1 = 0.139-0.172/(tr^(4.2));
b = r*tc/pc*(b0+omega*b1);
8
9
10
11
z = 1 + b*p/(r*t);
12
13
vig = v_ideal_gas(t,p_bar);
14
15
res = z*vig;
16
17
function res = v_ideal_gas( t,p_bar )
p = p_bar*1e5;
r = 8.314;
v = r*t/p;
res = v*(100^(3));
end
18
19
20
21
22
23
24
25
end
The top-level function, v_virial_2, is the outer function and v_ideal_gas is
an inner function.
Nesting functions is useful because the variables of the outer function can be
accessed from the inner function, and likewise the variables of the inner function
can be accessed by the outer function. This is not possible with parallel functions.
In this example, using a nested function makes it so that we no longer need to pass
the temperature and pressure to v_ideal_gas, and we additionally no longer
need to redefine the gas constant.
t Note that in the MATLAB
documentation, the outer
function is the parent function, and the inner function
(or functions) is the nested
function
386
Chapter 11 Second-order systems
Listing 11.3 v_virial_3.m
1
2
3
4
function res = v_virial_3( t,p_bar,tc,pc_bar,omega )
p = p_bar*1e5;
pc = pc_bar*1e5;
r = 8.314;
5
tr = t/tc;
6
7
b0 = 0.083-0.422/(tr^(1.6));
b1 = 0.139-0.172/(tr^(4.2));
b = r*tc/pc*(b0+omega*b1);
8
9
10
11
z = 1 + b*p/(r*t);
12
13
vig = v_ideal_gas();
14
15
res = z*vig;
16
17
function res = v_ideal_gas()
v = r*t/p;
res = v*(100^(3));
end
18
19
20
21
22
23
end
The function v_ideal_gas can access the necessary variables r, t, and p
from v_virial_3. This obviates the need to pass any variables to v_ideal_gas,
making it easier to test and debug v_virial_3. You will notice that when the
scope of a variable spans multiple functions, the MATLAB Editor’s default behavior is to color the variable cyan. That is, if a variable here is mutual to both
v_virial_3 and v_ideal_gas, it is colored cyan.
Before moving on, let’s repeat the calculation of Section 6.1 to ensure we get
the same result in all cases.
>> v_virial(440,60,425.125,37.960,0.201)
ans = 313.2382
>> v_virial_2(440,60,425.125,37.960,0.201)
ans = 313.2382
>> v_virial_3(440,60,425.125,37.960,0.201)
ans = 313.2382
11.1 Nested functions
387
11.1.1 Application fzero
As another application, let’s revisit our duck buoyancy problem, Example 7.2.
Specifically, let’s look at Listing 7.7 on page 228 which I have copied below:
Listing 11.4 duck2.m
1
2
3
4
5
6
7
8
% res = duck2(x,r,rho)
% Function to find the required depth of a duck to remain
% buoyant in water. duck2 requires that we pass parameters
% r (radius of duck) and rho (density of the liquid), in
% addition to the depth (x) which is what we will solve for.
function res = duck2( x,r,rho )
res = 0.3.*4.*r.^(3)-rho.*(3.*r.*x.^(2)-x.^(3));
end
In the problem we were solving for the depth (d ) to which a duck of radius r was
submerged in water of density ρ using fzero. fzero expects a function to which
we pass only a single variable. We therefore specified r and ρ in the function to
solve. In order to generalize the function to allow us to pass r and ρ too, we had
to use an anonymous function (see Section 7.16 on page 227) Here, let’s instead
use a nested function to simplify the task, and to use an unmodified call to fzero.
To accomplish this, I will create a top function duck_zero to which I will pass r
and ρ, and it will return the depth d . I will then nest duck2. In this way, duck2
can “see” the value of r and ρ in the top function, so that d is the only variable
that now needs to be passed. To solve for d , I will add the call to fzero to the top
function, and save the result to res to be returned. Let’s do it!
Listing 11.5 duck_zero.m
1
2
function res = duck_zero( r, rho )
res = fzero(@duck2,[0,r]);
3
function res = duck2( x )
res = 0.3*4*r^(3)-rho*(3*r*x^(2)-x^(3));
end
4
5
6
7
8
end
>> r = 10;
>> rho = 1;
>> d = duck_zero(r, rho)
d = 7.2651
This is in perfect agreement with our previous solution. We will look at an application to ode45, which has a restriction of two input variables, later in this chapter.
Application to fsolve is also straightforward.
t Remember, an anonymous
function is restricted to a
single executable statement.
There is no such restriction
in a nested function.
388
Chapter 11 Second-order systems
11.2 Newtonian motion
Newton’s second law of motion is often written like this
F = ma
where F is the net force acting on a object, m is the mass of the object, and a is the
resulting acceleration of the object. In a simple case where the object is moving
along a straight line, F and a are scalars, but in general they are vectors.
Even more generally, if F and a vary in time, then they can be thought of as
functions that return vectors; that is, F is a function and the result of evaluating
F (t ) is a vector that describes the net force at time t . So a more explicit way to
write Newton’s law is
~ (t ) = m~
∀t : F
a (t )
The arrangement of this equation suggests that if you know m and a you can
compute force, which is true, but in most physical simulations it is the other way
around. Based on a physical model, you know F and m, and compute a.
So if you know acceleration, a, as a function of time, how do you find the
position of the object, z? Well, we know that acceleration is the second derivative
of position, so we can write a differential equation
z 00 = a
Where a and z are functions of time that return vectors, and z 00 is the second time
derivative of z.
Because this equation includes a second derivative, it is a second-order ODE.
ode45 can’t solve this equation in this form, but by introducing a new variable, v,
for velocity, we can rewrite it as a system of first-order ODEs.
z0 = v
v0 = a
t Screen casts are available
for Professor Paluch working through the following
three sections: Freefall, Air
resistance, and Parachute.
They should be viewed as
a series of screen casts that
build-off of each other.
The first equation says that the first derivative of z is v; the second says that the
derivative of v is a.
Note that while in this chapter we will focus on examples from Newtonian
mechanics, the same strategy can be used in general to solve higher order (second
order and higher) ODEs; we can convert the ODE to a system of first order ODEs,
which we learned to solve in the previous chapter.
11.3 Freefall
Let’s start with a simple example, an object in freefall in a vacuum (where there’s
no air resistance). Near the surface of the earth, the acceleration of gravity is
g = −9.8 m/s2 , where here the minus sign indicates that gravity pulls down.
11.3 Freefall
389
If the object falls straight down (in the same direction as gravity), we can
describe its position with a scalar value, altitude. So this will be a one-dimensional
problem, at least for now.
Here is a rate function we can use with ode45 to solve this problem:
Listing 11.6 freefall.m
1
2
3
4
function res = freefall(t, X)
% Un-pack
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
5
% Compute rates
dzdt = v;
dvdt = acceleration(t, z, v);
6
7
8
9
10
11
12
end
% Pack the rates up as a column vector
res = [dzdt; dvdt];
13
14
15
16
17
function res = acceleration(t, z, v)
g = -9.8;
% acceleration of gravity in m/s^2
res = g;
end
The first function is the rate function. It gets t and X as input variables, where
the elements of X are understood to be position and velocity. The return value
from freefall is a (column) vector that contains the derivatives of position and
velocity, which are velocity and acceleration, respectively.
Computing z 0 is easy because we are given velocity as an element of X. The
only thing we have to compute is acceleration, which is what the second function
does. acceleration computes acceleration as a function of time, position and
velocity. In this example, the net acceleration is a constant, so we don’t really have
to include all this information yet, but we will soon.
Here’s how to run ode45 with this rate function:
>> ode45(@freefall, [0, 30], [4000, 0])
As always, the first argument is the function handle, the second is the time interval
(30 seconds) and the third is the initial condition: in this case, the initial altitude
is 4000 meters and the initial velocity is 0. So you can think of the “object” as a
skydiver jumping out of an airplane at about 12,000 feet.
Here’s what the result looks like:
390
Chapter 11 Second-order systems
4000
3500
3000
2500
2000
1500
1000
500
0
-500
0
5
10
15
20
25
30
Figure 11.1 ode45(@freefall,[0,30],[4000,0])
The bottom line shows velocity starting at zero and dropping linearly. The top
line shows position starting at 4000 m and dropping parabolically (but remember
that this parabola is a function of time, not a ballistic trajectory).
While in some cases it may be nice to plot both position and velocity on the
same graph using the same y-axis, this is not always the case. Let’s re-solve, storing to variables, and then plot each on its own graph.
>> [T,M] = ode45(@freefall, [0, 30], [4000, 0]);
>> Z = M(:,1);
>> V = M(:,2);
We can now readily plot position versus time and velocity versus time, each in
their own graph. However, they do have a common x-axis, so it may be nice to
“stack” the graphs. By this, I mean create two graphs, but have them lined up in a
column. Since we have two graphs, we can think of this as a single column with
two rows. Here is how we do it:
>>
>>
>>
>>
>>
>>
>>
>>
figure
subplot(2,1,1) % First plot in 2 by 1 matrix
plot(T,Z,'-r')
ylabel('position [m]')
subplot(2,1,2) % Second plot in 2 by 1 matrix
plot(T,V,'-b')
ylabel('velocity [m/s]')
xlabel('time [s]')
11.3 Freefall
391
Here’s what the result looks like:
position [m]
4000
2000
0
-2000
0
5
10
0
5
10
15
20
25
30
15
20
25
30
velocity [m/s]
0
-100
-200
-300
time [s]
Figure 11.2 Creating a stacked plot of our results.
Notice that ode45 doesn’t know where the ground is, so the skydiver keeps
going through zero into negative altitude. No worries, we will fix this.
Let’s solve for the time it takes to hit the ground. Contact is made when
z = 0. Remember when we use ode45, the only times we know exactly are the
initial and final time. So let’s modify freefall by adding a new top function
freefall_time, such that the user can pass the final time and the function
returns z at the final time. We can then use fzero with this function to find the
time at which contact is made.
392
Chapter 11 Second-order systems
Listing 11.7 freefall_time.m
1
2
3
4
5
6
7
function res = freefall_time(t)
% Solve over the range 0 to t
[T,M] = ode45(@freefall, [0, t], [4000, 0]);
% The first column of M is position, the second is velocity
Z = M(:,1);
res = Z(end);
end
8
9
10
11
12
function res = freefall(t, X)
% Un-pack
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
13
% Compute rates
dzdt = v;
dvdt = acceleration(t, z, v);
14
15
16
17
18
19
20
end
% Pack the rates into a column vector
res = [dzdt; dvdt];
21
22
23
24
25
function res = acceleration(t, z, v)
g = -9.8;
% acceleration of gravity in m/s^2
res = g;
end
>> t_contact = fzero(@freefall_time,[0.1,30])
t_contact = 28.5714
Great! Let’s keep going. Let’s update our function so that the fzero call
is contained in the function file. To do this, let’s create a new top function
freefall_contact. The function need not take any input variables, but it will
return the time it takes to make contact.
11.3 Freefall
393
Listing 11.8 freefall_contact.m
1
2
3
4
function res = freefall_contact()
t_contact = fzero(@freefall_time,[0.1,30]);
res = t_contact;
end
5
6
7
8
9
10
11
12
function res = freefall_time(t)
% Solve over the range 0 to t
[T,M] = ode45(@freefall, [0, t], [4000, 0]);
% The first column of M is position, the second is velocity
Z = M(:,1);
res = Z(end);
end
13
14
15
16
17
function res = freefall(t, X)
% Un-pack
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
18
% Compute rates
dzdt = v;
dvdt = acceleration(t, z, v);
19
20
21
22
23
24
25
end
% Pack the rates into a column vector
res = [dzdt; dvdt];
26
27
28
29
30
function res = acceleration(t, z, v)
g = -9.8;
% acceleration of gravity in m/s^2
res = g;
end
>> t_contact = freefall_contact()
t_contact = 28.5714
Nice! What’s that, keep going? Okay! Knowing the time at which we make
contact is great, but it would be even cooler if we knew the velocity too. We can
do this two ways, the second will be more efficient than the first. But first, since
we now know the time of contact, we could use this to call ode45 again, use this
value as the final time, and then return the contact time and velocity. Let’s do it!
394
Chapter 11 Second-order systems
Listing 11.9 freefall_contact_b.m
1
2
3
4
5
6
function [t_contact, v_contact] = freefall_contact_b()
t_contact = fzero(@freefall_time,[0.1,30]);
[T,M] = ode45(@freefall, [0, t_contact], [4000, 0]);
V = M(:,2);
v_contact = V(end);
end
7
8
9
10
11
12
13
function res = freefall_time(t)
[T,M] = ode45(@freefall, [0, t], [4000, 0]);
% The first column of M is position, the second is velocity
Z = M(:,1);
res = Z(end);
end
14
15
16
17
function res = freefall(t, X)
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
18
dzdt = v;
dvdt = acceleration(t, z, v);
19
20
21
22
23
end
res = [dzdt; dvdt];
% pack the results in a column vector
24
25
26
27
28
function res = acceleration(t, z, v)
g = -9.8;
% acceleration of gravity in m/s^2
res = g;
end
>> [sol_t, sol_v] = freefall_contact_b()
sol_t = 28.5714
sol_v = -280
Note that 280 m/s is 626.342 miles per hour, ouch!
The second, more efficient way to solve this problem is using a nested function. We know that the last time fzero will call freefall_time is when the
solution is found. Therefore, the last row of M contains our desired solution. Remember that when we nest a function, the inner function can see the workspace of
outer function, and the outer function can see the workspace of the inner function.
Therefore, if we nest freefall_time in freefall_contact, freefall_contact
will see the final value of M, and we therefore do not need to call ode45 an additional time. (This is what makes it more efficient.)
11.3 Freefall
395
Listing 11.10 freefall_contact_c.m
1
function [t_contact,v_contact] = freefall_contact_c()
2
t_contact = fzero(@freefall_time,[0.1,30]);
V = M(:,2);
v_contact = V(end);
3
4
5
6
function res = freefall_time(t)
[T,M] = ode45(@freefall, [0, t], [4000, 0]);
% The first column of M is position, the second is velocity
Z = M(:,1);
res = Z(end);
end
7
8
9
10
11
12
13
14
end
15
16
17
18
function res = freefall(t, X)
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
19
dzdt = v;
dvdt = acceleration(t, z, v);
20
21
22
23
24
end
res = [dzdt; dvdt];
% pack the results in a column vector
25
26
27
28
29
function res = acceleration(t, z, v)
g = -9.8;
% acceleration of gravity in m/s^2
res = g;
end
>> [sol_t, sol_v] = freefall_contact_c()
sol_t = 28.5714
sol_v = -280
Perfect! And since freefall_contact will see the final value of T and M, we could
plot the complete trajectory if we would like.
Running freefall_contact_c MATLAB R2018a additionally prints the following warning message:
396
Chapter 11 Second-order systems
Warning: File: freefall_contact_c.m Line: 7 Column: 12
Defining "M" in the nested function shares it with the parent function.
In a future release, to share "M" between parent and nested functions,
explicitly define it in the parent function.
This is a pretty clear warning message, and only started to appear when I upgraded to MATLAB 2018a. Essentially, if we are using a nested function to share
variables, all shared variables should be created in the outer (parent) function.
And in future releases “should” will become a “must,” so we should get in the
habit of doing so.
Listing 11.11 freefall_contact_d.m
1
2
3
4
5
6
7
function [t_contact,v_contact] = freefall_contact_d()
% We can use empty brackets to define a variable without assigning it a
% value. This corresponds to an empty matrix.
M = [];
t_contact = fzero(@freefall_time,[0.1,30]);
V = M(:,2);
v_contact = V(end);
8
function res = freefall_time(t)
[T,M] = ode45(@freefall, [0, t], [4000, 0]);
% The first column of M is position, the second is velocity
Z = M(:,1);
res = Z(end);
end
9
10
11
12
13
14
15
16
end
17
18
19
20
function res = freefall(t, X)
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
21
dzdt = v;
dvdt = acceleration(t, z, v);
22
23
24
25
26
end
res = [dzdt; dvdt];
% pack the results in a column vector
27
28
29
30
31
function res = acceleration(t, z, v)
g = -9.8;
% acceleration of gravity in m/s^2
res = g;
end
11.4 Air resistance
397
I created a variable without assigning a value using empty brackets; this is equivalent to creating an empty matrix. Note that if I had instead used M =1 instead, the
result is unchanged.
As a very final note before moving on, if you wished, you could nest acceleration
within freefall. The advantage of doing so would be that you would no longer
need to pass t, z, and v.
t Screen casts are available
11.4 Air resistance
To make this simulation more realistic, we can add air resistance (or drag). For
large objects moving quickly through air, the force due to air resistance, called
“drag,” is proportional to v 2 :
for Professor Paluch working through the following
three sections: Freefall, Air
resistance, and Parachute.
They should be viewed as
a series of screen casts that
build-off of each other.
F drag = c v 2
Where c is a drag constant that depends on the density of air, the cross-sectional
area of the object and the surface properties of the object. For the purpose of this
problem, let’s say that c = 0.2. To convert from force to acceleration, we have
to know mass, so let’s say that the skydiver (with equipment) weighs 75 kg. The
acceleration can then be computed using Newton’s second law: a drag = F drag /m.
Here’s a version of acceleration that takes air resistance into account (you
don’t have to make any changes in freefall, I just renamed it freefall_2 so as
not to be confused with the case of no drag). Recall that while gravity is pulling
the skydiver down, drag acts in the opposite direction of motion; in this case
where the skydiver is falling down, drag is pushing the skydiver up.
Listing 11.12 freefall_2.m
1
2
3
function res = freefall_2(t, X)
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
4
5
6
dzdt = v;
dvdt = acceleration(t, z, v);
7
8
9
10
11
12
13
14
15
16
17
res = [dzdt; dvdt];
% pack
end
function res = acceleration(t, z,
a_grav = -9.8;
%
c = 0.2;
%
m = 75;
%
f_drag = c * v^2;
%
a_drag = f_drag / m;
%
res = a_grav + a_drag;
%
end
the results in a column vector
v)
acceleration of gravity in m/s^2
drag constant
mass in kg
drag force in N
drag acceleration in m/s^2
total acceleration
t In your fluid mechanics
class you likely saw the drag
equation: F drag = 12 ρv 2C D A,
where ρ is the density of the
fluid (here air), C D is the
drag coefficient which is a
function of Reynold’s number, and A is the reference
cross-sectional area (here
the cross-sectional area
of the skydiver). We lump
these parameters into a
single parameter (drag constant), c for convenience.
398
Chapter 11 Second-order systems
The sign of the drag force (and acceleration) is positive as long as the object is
falling, the direction of the drag force is up; the drag force always acts opposite
to the direction of motion (or the velocity). The net acceleration is the sum of
gravity and drag. Be careful when you are working with forces and accelerations;
make sure you only add forces to forces or accelerations to accelerations. In my
code, I use comments to remind myself what units the values are in. That helps
me avoid nonsense like adding forces to accelerations.
Here’s what the result looks like with air resistance:
>> ode45(@freefall_2, [0, 30], [4000, 0])
4000
3500
3000
2500
2000
1500
1000
500
0
-500
0
5
10
15
20
25
30
Figure 11.3 ode45(@freefall_2,[0,30],[4000,0])
Big difference! With air resistance, velocity increases until the drag acceleration
equals g ; after that, velocity is a constant, known as “terminal velocity,” and
position decreases linearly (and much more slowly than it would in a vacuum).
To examine the results more closely, we can assign the position and velocity to
variables
>> [T, M] = ode45(@freefall_2, [0, 30], [4000, 0]);
And let’s again create a stacked plot.
>>
>>
>>
>>
Z = M(:,1);
V = M(:,2);
figure
subplot(2,1,1) % First plot in 2 by 1 matrix
11.4 Air resistance
>>
>>
>>
>>
>>
>>
399
plot(T,Z,'-r')
ylabel('position [m]')
subplot(2,1,2) % Second plot in 2 by 1 matrix
plot(T,V,'-b')
ylabel('velocity [m/s]')
xlabel('time [s]')
Here’s what the result looks like:
position [m]
4000
3500
3000
2500
2000
0
5
10
0
5
10
15
20
25
30
15
20
25
30
velocity [m/s]
0
-20
-40
-60
-80
time [s]
Figure 11.4 Creating a stacked plot of our results with air resistance.
Lastly, let’s update freefall_c to use our updated acceleration file to solve
for the time to contact and the corresponding velocity.
400
Chapter 11 Second-order systems
Listing 11.13 freefall_2_contact.m
1
2
3
4
5
function [t_contact,v_contact] = freefall_2_contact()
M = []; % Creating variable without assigning a value.
t_contact = fzero(@freefall_time,[0.1,90]);
V = M(:,2);
v_contact = V(end);
6
function res = freefall_time(t)
[T,M] = ode45(@freefall, [0, t], [4000, 0]);
% The first column of M is position, the second is velocity
Z = M(:,1);
res = Z(end);
end
7
8
9
10
11
12
13
14
end
15
16
17
18
function res = freefall(t, X)
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
19
20
21
dzdt = v;
dvdt = acceleration(t, z, v);
22
23
24
25
26
27
28
29
30
31
32
res = [dzdt; dvdt];
% pack the results in a column vector
end
function res = acceleration(t, z, v)
a_grav = -9.8;
% acceleration of gravity in m/s^2
c = 0.2;
% drag constant
m = 75;
% mass in kg
f_drag = c * v^2;
% drag force in N
a_drag = f_drag / m;
% drag acceleration in m/s^2
res = a_grav + a_drag;
% total acceleration
end
>> [t_contact,v_contact] = freefall_2_contact()
t_contact = 70.2693
v_contact = -60.6218
We find that it takes longer to contact the ground, and the velocity at impact is
smaller. (Here it is 135.6 miles per hour.) The final velocity here corresponds to
the terminal velocity.
11.4 Air resistance
401
Example 11.1
Let’s increase the mass of the skydiver and confirm that terminal velocity increases.
This relationship is the source of the intuition that heavy objects fall faster; in
air, they do! (In vacuum, they don’t! Have you ever seen the famous Apollo 15
hammer and feather experiment?) In your solution, you can just update the mass
in acceleration. But could you also think of how to allow the user the specify
the mass in the Command Window?
Solution: Let’s double the mass from 75 kg to 150 kg. To solve, let’s update
freefall_2_contact to use the larger mass, and to also create a stacked plot of
the position and velocity versus time.
Listing 11.14 freefall_3_contact.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function [t_contact,v_contact] = freefall_3_contact()
M = []; T=[]; Z=[]; % Creating variables without assigning a value.
t_contact = fzero(@freefall_time,[0.1,90]);
V = M(:,2);
v_contact = V(end);
% Now create the stacked plot too. Here the final
% time will be the contact time.
figure
subplot(2,1,1) % First plot in 2 by 1 matrix
plot(T,Z,'-r')
ylabel('position [m]')
subplot(2,1,2) % Second plot in 2 by 1 matrix
plot(T,V,'-b')
ylabel('velocity [m/s]')
xlabel('time [s]')
print('-depsc','freefall_stack_3.eps')
function res = freefall_time(t)
[T,M] = ode45(@freefall, [0, t], [4000, 0]);
% The first column of M is position, the second is velocity
Z = M(:,1);
res = Z(end);
end
end
function res = freefall(t, X)
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
dzdt = v;
dvdt = acceleration(t, z, v);
end
res = [dzdt; dvdt];
% pack the results in a column vector
402
Chapter 11 Second-order systems
function res = acceleration(t, z, v)
a_grav = -9.8;
% acceleration of gravity in m/s^2
c = 0.2;
% drag constant
m = 150;
% mass in kg
f_drag = c * v^2;
% drag force in N
a_drag = f_drag / m;
% drag acceleration in m/s^2
res = a_grav + a_drag;
% total acceleration
end
Solving:
>> [t_sol,v_sol] = freefall_3_contact()
t_sol = 52.7186
v_sol = -85.7311
So the heavier person takes less time to contact the ground, and has a higher
terminal velocity. And here is the resulting plot:
4000
position [m]
3000
2000
1000
0
0
10
20
0
10
20
30
40
50
60
30
40
50
60
0
-20
velocity [m/s]
37
38
39
40
41
42
43
44
-40
-60
-80
-100
time [s]
Figure 11.5 Creating a stacked plot of our results with air resistance where
we have doubled the mass of the object.
We again find that after a short period of time the position decreases linearly.
Now how could we allow the user to specify the mass in the Command Window? Well, the only function we can call from the Command Window is the top
function, freefall_3_contact. The mass will therefore need to be passed to
freefall_3_contact. And then to get the mass to acceleration, we can make
sure it is nested in the top function. But we can not nest just acceleration.
Since it is called by freefall, we will need to nest it too. So in the final version of my code, I will rename the top function freefall_3m_contact, and
11.4 Air resistance
403
freefall_time, freefall, and acceleration will all be parallel with each other
(at the same level) but nested in freefall_3m_contact. Also, I add as inputs to
freefall_3m_contact the mass, m, and a string that will contain the name of the
resulting figure, fname. Also, within acceleration we delete the line where the
mass was previously assigned.
Listing 11.15 freefall_3m_contact.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function [t_contact,v_contact] = freefall_3m_contact(m,fname)
M = []; T=[]; Z=[]; % Creating variables without assigning a value.
t_contact = fzero(@freefall_time,[0.1,90]);
V = M(:,2);
v_contact = V(end);
% Now create the stacked plot too. Here the final
% time will be the contact time.
figure
subplot(2,1,1) % First plot in 2 by 1 matrix
plot(T,Z,'-r')
ylabel('position [m]')
subplot(2,1,2) % Second plot in 2 by 1 matrix
plot(T,V,'-b')
ylabel('velocity [m/s]')
xlabel('time [s]')
print('-depsc',fname)
function res = freefall_time(t)
[T,M] = ode45(@freefall, [0, t], [4000, 0]);
% The first column of M is position, the second is velocity
Z = M(:,1);
res = Z(end);
end
function res = freefall(t, X)
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
dzdt = v;
dvdt = acceleration(t, z, v);
end
res = [dzdt; dvdt];
% pack the results in a column vector
function res = acceleration(t, z, v)
a_grav = -9.8;
% acceleration of gravity in m/s^2
c = 0.2;
% drag constant
f_drag = c * v^2;
% drag force in N
a_drag = f_drag / m;
% drag acceleration in m/s^2
res = a_grav + a_drag;
% total acceleration
end
404
Chapter 11 Second-order systems
end
Solving:
>> >> [t_sol,v_sol] = freefall_3m_contact(75,'freefall_3_75m.eps')
t_sol = 70.2693
v_sol = -60.6218
4000
position [m]
3000
2000
1000
0
-1000
0
10
20
30
0
10
20
30
40
50
60
70
80
40
50
60
70
80
0
velocity [m/s]
44
-20
-40
-60
time [s]
Figure 11.6 [t_sol,v_sol] =
freefall_3m_contact(75,’freefall_3_75m.eps’)
>> >> [t_sol2,v_sol2] = freefall_3m_contact(150,'freefall_3_150m.eps')
t_sol2 = 52.7186
v_sol2 = -85.7311
11.5 Parachute!
405
4000
position [m]
3000
2000
1000
0
0
10
20
0
10
20
30
40
50
60
30
40
50
60
0
velocity [m/s]
-20
-40
-60
-80
-100
time [s]
Figure 11.7 [t_sol2,v_sol2] =
freefall_3m_contact(150,’freefall_3_150m.eps’)
Nice!
11.5 Parachute!
In the previous section, we saw that the terminal velocity of a 75 kg skydiver is
about 60 m/s, which is about 130 mph. If you hit the ground at that speed, you
would almost certainly be killed. That’s where parachutes come in.
Example 11.2
Modify acceleration so that after 30 seconds of free-fall the skydiver deploys a
parachute, which (almost) instantly increases the drag constant to 2.7.
What is the terminal velocity now? How long (after deployment) does it take to
reach the ground?
Solution: Let’s update the acceleration function in freefall_3_contact such
that when t > 30 the drag constant is 2.7. We will also restore the mass back to 75
kg, and with the use of a parachute, we expect it to take longer to hit the ground.
We will therefore increase the upper bracket initially used by fzero. We will name
this new file parachute_contact.
t Screen casts are available
for Professor Paluch working through the following
three sections: Freefall, Air
resistance, and Parachute.
They should be viewed as
a series of screen casts that
build-off of each other.
406
Chapter 11 Second-order systems
Listing 11.16 parachute_contact.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function [t_contact,v_contact] = parachute_contact()
M = []; T=[]; Z=[]; % Creating variables without assigning a value.
t_contact = fzero(@freefall_time,[0.1,300]);
V = M(:,2);
v_contact = V(end);
% Now create the stacked plot too. Here the final
% time will be the contact time.
figure
subplot(2,1,1) % First plot in 2 by 1 matrix
plot(T,Z,'-r')
ylabel('position [m]')
subplot(2,1,2) % Second plot in 2 by 1 matrix
plot(T,V,'-b')
ylabel('velocity [m/s]')
xlabel('time [s]')
print('-depsc','parachute.eps')
function res = freefall_time(t)
[T,M] = ode45(@freefall, [0, t], [4000, 0]);
% The first column of M is position, the second is velocity
Z = M(:,1);
res = Z(end);
end
end
function res = freefall(t, X)
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
dzdt = v;
dvdt = acceleration(t, z, v);
res = [dzdt; dvdt];
% pack the results in a column vector
end
function res = acceleration(t, z, v)
a_grav = -9.8;
% acceleration of gravity in m/s^2
c = 0.2;
% drag constant
% After 30 seconds the parachute is deployed
if t > 30
c = 2.7;
end
m = 75;
% mass in kg
f_drag = c * v^2;
% drag force in N
a_drag = f_drag / m;
% drag acceleration in m/s^2
res = a_grav + a_drag;
% total acceleration
end
11.5 Parachute!
407
Solving:
>> [t_sol,v_sol] = parachute_contact()
t_sol = 176.5233
v_sol = -16.5020
So with the parachute it takes much longer to hit the ground, and the terminal
velocity is much smaller. The value of 16.5 m/s corresponds to approximately 37
miles per hour. And here is the resulting plot:
4000
position [m]
3000
2000
1000
0
-1000
0
20
40
60
80
100
120
140
160
180
0
20
40
60
80
100
120
140
160
180
velocity [m/s]
0
-20
-40
-60
time [s]
Figure 11.8 Creating a stacked plot parachute example.
The plots are very cool. After a short time, we reach our terminal velocity. The
velocity is constant and the position decreases linear. Then at 30 seconds we
deploy the parachute. We get a step change in the velocity, which quickly reaches
a new terminal velocity. The position continues to decrease linearly, only the slope
is smaller because the new terminal velocity is smaller in magnitude.
This solution is good and correct, and updating our code was very straightforward.
However, given that we have a step change in the drag constant at 30 s, we could
alternatively use ode45 to solve from 0 to 30 s, then take the conditions at 30 s and
use ode45 to solve from 30 s to the final time. When we solve from 0 to 30 s we
will use a a drag constant of 0.2. Then from 30 s to the final time we will use a drag
constant of 2.7. By breaking up the integration (ode45) into these two step, we
can avoid having a step change in the drag constant within the integration range.
To facilitate switching from a drag constant of 0.2 to 2.7, I will nest the functions
freefall and acceleration within parachute_2 so that they can all see the
flag variable defined in parachute_2 to indicate which drag constant should be
used. Here I will only solve for the contact time using fzero.
408
Chapter 11 Second-order systems
Listing 11.17 parachute_2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function res = parachute_2(tfinal)
t0 = 0; % initial time
t1 = 30;% end of first integration range
z0 = 4000; % initial altitude
v0 = 0; % initial velocity
flag = 0; % use c = 0.2;
[T, M] = ode45(@freefall, [t0, t1], [z0, v0]);
v1 = M(end,2); % velocity after 30 s
z1 = M(end,1); % altitude after 30 s
flag = 1; % use c = 2.7
[T, M] = ode45(@freefall, [t1, tfinal], [z1, v1]);
res = M(end,1); % altitude at time t
% Uncomment when you want to call parachute
% and display the velocity.
%M(end,2)
function res = freefall(t, X)
z = X(1);
% the first element is position
v = X(2);
% the second element is velocity
dzdt = v;
dvdt = acceleration(t,z,v);
end
res = [dzdt; dvdt];
% pack the results in a column vector
function res = acceleration(t,z,v)
a_grav = -9.8;
% acceleration of gravity in m/s^2
if flag == 0
% solving from 0 to 30 s
c = 0.2;
else
c = 2.7;
% solving from 30 s to tfinal
end
m = 75;
% mass in kg
f_drag = c * v^2;
% drag force in N
a_drag = f_drag / m;
% drag acceleration in m/s^2
res = a_grav + a_drag;
% total acceleration
end
end
Solving in this manner we have:
>> sol = fzero(@parachute_2,150)
sol = 176.5302
In this case we have only a very small change of 0.0069 s. So we see that ode45 is
11.6 Two dimensions
409
robust and adaptive, and can perform well even when integrating step changes.
So the moral of the story is, here I would stick with using parachute_contact.
11.6 Two dimensions
So far we have used ode45 for a system of first-order equations and for a single second-order equation. The next logical step is a system of second-order
equations, and the next logical example is a projectile. A “projectile” is an object
propelled through space, usually toward, and often to the detriment of, a target. If
a projectile stays in a plane, we can think of the system as two-dimensional, with
x representing the horizontal distance traveled and y representing the height or
altitude. So now instead of a skydiver, think of a circus performer being fired out
of a cannon (a “human cannonball”).
According to Wikipedia, the record distance for a human cannonball is 61.06
meters (just over 200 feet).
11.6.1 Flying in vacuum
Here is a general framework for the rate function for computing the trajectory
of a projectile in two dimensions using ode45. We will begin with a projectile in
vacuum, and then add air resistance (drag) later.
Listing 11.18 projectile.m
1
2
3
function res = projectile(t, W)
P = W(1:2);
V = W(3:4);
4
dPdt = V;
dVdt = acceleration(t, P, V);
5
6
7
8
9
end
res = [dPdt; dVdt];
10
11
12
13
14
function res = acceleration(t, P, V)
g = 9.8;
% acceleration of gravity in m/s^2
res = [0; -g];
end
The second argument of the rate function is a vector, W, with four elements. The
first two are assigned to P, which represents position; the last two are assigned
to V, which represents velocity. P and V are vectors with elements for the x and y
components. I perform a “partial” un-packing of W because the governing ODEs in
410
Chapter 11 Second-order systems
the x and y direction will be the same. The only difference will be the acceleration
in each direction, but that will be handled in the separate acceleration function.
The result from acceleration is also a vector; ignoring air resistance (for
now), the acceleration in the x direction is 0; in the y direction it’s −g . Other than
that, this code is similar to what we saw in Section 11.3.
Note that if you wished to perform a “complete” un-packing of W you could,
and here is what that might look like:
Listing 11.19 projectile_a.m
1
2
3
4
5
6
7
function res = projectile_a(t, W)
% Position, x then y
px = W(1);
py = W(2);
% Velocity, x then y
vx = W(3);
vy = W(4);
8
% Computing rates
dpxdt = vx;
dpydt = vy;
dvxdt = acceleration_x(t,px,vx);
dvydt = acceleration_y(t,py,vy);
9
10
11
12
13
14
15
16
17
end
% Packing up the rates
res = [dpxdt; dpydt; dvxdt; dvydt];
18
19
20
21
function res = acceleration_x(t, px, vx)
res = 0;
end
22
23
24
25
26
function res = acceleration_y(t, py, vy)
g = 9.8;
% acceleration of gravity in m/s^2
res = -g;
end
Use what makes most sense to you, but I will build off of projectile.
If we launch the human cannonball from an initial height of 3 meters (x 0 = 0
m, y 0 = 3 m), with initial velocities of 40 m/s and 40 m/s in the x and y direction
(v x,0 = v y,0 = 40 m/s), the ode45 call would look like this:
>> ode45(@projectile, [0,10], [0, 3, 40, 40]);
11.6 Two dimensions
411
And the result looks like this:
400
350
300
250
200
150
100
50
0
-50
-100
0
2
4
6
8
10
Figure 11.9 ode45(@projectile,[0,10],[0,3,40,40])
You might have to think a little to figure out which line is which, but we will try to
clarify that momentarily. But it looks like the flight time is about 6 seconds.
Let’s make three improvements to our code. 1) First, it is unlikely that we are
provided with values of the initial velocity in the x and y direction. It is more likely
that we are provided with the initial velocity (v 0 , or speed) and the launch angle
θ0 . We could use this to compute the x and y component as: v x,0 = v 0 cos (θ0 )
and v y,0 = v 0 sin (θ0 ). 2) Let’s add a top function projectile_model that for now
takes no input, but solves the system of ODEs, generates plots, and returns the
values of our functions back to the Command Window. 3) Plot the trajectory in
the x y plain.
Let’s update our code, and solve for the same conditions as before, but here
let’s use an initial velocity of 60 m/s with a launch angle of 45 degrees.
412
Chapter 11 Second-order systems
Listing 11.20 projectile_model.m
1
function [T,X,Y,VX,VY] = projectile_model()
2
% The initial position in m
x0 = 0;
y0 = 3;
3
4
5
6
% The initial velocity in m/s and launch angle in degrees
v0 = 60;
theta0 = 45;
7
8
9
10
% Computing the x and y component of the initial velocity
vx0 = v0*cosd(theta0);
vy0 = v0*sind(theta0);
11
12
13
14
% The time range to solve over in s
t0 = 0;
tfinal = 10;
15
16
17
18
% Solving our ODE
[T,M] = ode45(@projectile,[t0,tfinal],[x0,y0,vx0,vy0]);
19
20
21
% Let's break M up. The columns will correspond to our functions in the
% order x,y,vx,vy, the same order used by the initial conditions and
% within the rate function.
X = M(:,1);
Y = M(:,2);
VX = M(:,3);
VY = M(:,4);
22
23
24
25
26
27
28
29
% Next, let's plot the trajectory in the xy plane
figure(1)
plot(X,Y,'-k')
xlabel('x position [m]')
ylabel('y position [m]')
title('Trajectory in xy plan')
print('-depsc','xy_trajectory.eps')
30
31
32
33
34
35
36
37
38
end
39
40
41
42
43
function res = projectile(t, W)
P = W(1:2);
V = W(3:4);
11.6 Two dimensions
413
dPdt = V;
dVdt = acceleration(t, P, V);
44
45
46
47
48
end
res = [dPdt; dVdt];
49
51
52
53
function res = acceleration(t, P, V)
g = 9.8;
% acceleration of gravity in m/s^2
res = [0; -g];
end
>> [T,X,Y,VX,VY] = projectile_model();
Trajectory in xy plan
100
80
60
40
y position [m]
50
20
0
-20
-40
-60
-80
0
50
100
150
200
250
300
350
400
450
x position [m]
Figure 11.10 projectile_model()
Cool! When we solved and stored the results to variables, T was a vector of times
at which our functions were evaluated. M was again a matrix. Each column of M
corresponds to a function, in the order that we are solving. Columns 1 to 4 are
x, y, v x , and v y , respectively, where v x and v y correspond to the velocity in the
x and y direction, respectively. The rows of M correspond to the rows (times) of
T at which the functions were evaluated. The order of the columns agreed with
the order of our functions when specifying our initial conditions, the order of
un-packing the input vector to our rate function, and the order of the vector of
computed rates in our rate function.
We solved over the range of 0 to 10 s. When solving using ode45 we need
to specify a time range. However, this is an example where we do not know
414
Chapter 11 Second-order systems
the correct final time to use. We see that toward the end of the trajectory the y
position is negative. This would correspond to the human cannonball tunneling
through the Earth! Next, let’s update our code so that our trajectory ends when
contact is made with Earth. This would correspond to when y = 0.
To accomplish this, I re-save projectile_model as projectile_model_2,
and update the name of the top function accordingly. I then nest within this top
function an error function named projectile_solve. Within this error function
I solve the system of ODEs over the range t 0 to t , and return the y position at t .
This will allow me to add an fzero call in the top function to solve for t when
y = 0. The last time fzero calls the error function with when we find the desired
t . By nesting, we save a final call to ode45 and it facilitates passing parameters to
the error function. Here is my updated code and solution.
Listing 11.21 projectile_model_2.m
1
function [T,X,Y,VX,VY] = projectile_model_2()
2
3
4
5
% The initial position in m
x0 = 0;
y0 = 3;
6
7
8
9
% The initial velocity in m/s and launch angle in degrees
v0 = 60;
theta0 = 45;
10
11
12
13
% Computing the x and y component of the initial velocity
vx0 = v0*cosd(theta0);
vy0 = v0*sind(theta0);
14
15
16
17
% The initial time. We will solve for the final time as the time
% required to make contact, y=0. (This criteria can also be updated.)
t0 = 0;
18
19
20
21
22
23
24
25
26
% Solving for the time required to make contact using fzero. The error
% function will be nested. The final time fzero calls the error
% function will correspond to when t is when contact is made. By
% nesting we will not need to resolve at this time. The error function
% is just solving the ODEs over the rante t0 to t, and returning the y
% position at t.
M = []; % Creating variable M without assign a value.
t_impact = fzero(@projectile_solve,[0.1,100]);
27
28
29
30
% Let's break M up. The columns will correspond to our functions in the
% order x,y,vx,vy, the same order used by the initial conditions and
% within the rate function.
11.6 Two dimensions
X = M(:,1);
Y = M(:,2);
VX = M(:,3);
VY = M(:,4);
31
32
33
34
35
% Next, let's plot the trajectory in the xy plane
figure(1)
plot(X,Y,'-k')
xlabel('x position [m]')
ylabel('y position [m]')
title('Trajectory in xy plan')
print('-depsc','xy_trajectory.eps')
36
37
38
39
40
41
42
43
function res = projectile_solve(t)
% Solving our ODE over the range t0 to t
[T,M] = ode45(@projectile,[t0,t],[x0,y0,vx0,vy0]);
44
45
46
47
48
49
end
50
% Returning the y position at t
res = M(end,2);
51
52
end
53
54
55
56
function res = projectile(t, W)
P = W(1:2);
V = W(3:4);
57
dPdt = V;
dVdt = acceleration(t, P, V);
58
59
60
61
62
end
res = [dPdt; dVdt];
63
64
65
66
67
function res = acceleration(t, P, V)
g = 9.8;
% acceleration of gravity in m/s^2
res = [0; -g];
end
>> [T,X,Y,VX,VY] = projectile_model_2();
415
416
Chapter 11 Second-order systems
Trajectory in xy plan
100
90
80
y position [m]
70
60
50
40
30
20
10
0
0
50
100
150
200
250
300
350
400
x position [m]
Figure 11.11 projectile_model_2()
Excellent!
Building off of this code, the number of questions we can ask is endless. One
particularly fun question would be for an initial velocity v 0 , what is the optimal
launch angle? From our general physics class we know this is 45 degrees. But
can we demonstrate that here? I start by saving a copy of projectile_model_2
as projectile_model_angle, update the name of the top function accordingly,
and update the function so that it only returns the x distance traveled and does
not generate a plot.
Great. So now we have a function that will return the distance traveled for
a given initial launch angle. Next, we just build upon it to create a vector of
initial angles which we will loop over and compute the distance traveled for each
case. We will then plot the distance traveled versus launch angle, and return the
appropriate vectors. Here it is:
11.6 Two dimensions
Listing 11.22 projectile_model_angle.m
1
function [Theta0,X_impact] = projectile_model_angle()
2
3
4
5
% The initial position in m
x0 = 0;
y0 = 3;
6
7
8
% The initial velocity in m/s
v0 = 60;
9
10
11
% Vector of initial launch angles in degrees
Theta0 = 30:1:60;
12
13
14
15
% Computing the x and y component of the initial velocity
Vx0 = v0*cosd(Theta0);
Vy0 = v0*sind(Theta0);
16
17
18
19
% The initial time. We will solve for the final time as the time
% required to make contact, y=0. (This criteria can also be updated.)
t0 = 0;
20
21
22
% Creating a vector to store the distance travled to
X_impact = zeros(1,length(Theta0));
23
24
25
26
27
28
29
30
31
% Looping over initial launch angles and solving for the distance
% traveled. This will be collected in a matrix.
M = []; % Creating variables without assiging a value
for i = 1:length(Theta0)
t_impact = fzero(@projectile_solve,[0.1,100]);
% Collect the x distance travled at impact
X_impact(i) = M(end,1);
end
32
33
34
35
36
37
% Generate a plot of distance travled vs launch angle
plot(Theta0,X_impact,'-ok')
xlabel('Launch angle [degrees]')
ylabel('Distance traveled [m]')
print('-depsc','optimal_launch_angle.eps')
38
39
40
41
function res = projectile_solve(t)
% Solving our ODE over the range t0 to t
[T,M] = ode45(@projectile,[t0,t],[x0,y0,Vx0(i),Vy0(i)]);
42
43
% Returning the y position at t
417
418
Chapter 11 Second-order systems
44
end
45
res = M(end,2);
46
47
end
48
49
50
51
function res = projectile(t, W)
P = W(1:2);
V = W(3:4);
52
dPdt = V;
dVdt = acceleration(t, P, V);
53
54
55
56
57
end
res = [dPdt; dVdt];
58
60
61
62
function res = acceleration(t, P, V)
g = 9.8;
% acceleration of gravity in m/s^2
res = [0; -g];
end
>> [Theta0,X_impact] = projectile_model_angel();
380
370
360
Distance traveled [m]
59
350
340
330
320
310
30
35
40
45
50
55
60
Launch angle [degrees]
Figure 11.12 projectile_model_angle()
From the plot we find that the maximum distance traveled is with an initial launch
angle of 45 degrees. If you wanted to identify the maximum in the resulting vec-
11.6 Two dimensions
419
tors (which could be included in your code), you can use the built-in function
max, which returns the maximum element of a vector.
>> max_x_dist = max(X_dist)
max_x_dist = 370.3228
>> theta_opt = Theta0( X_impact==max_x_dist )
theta_opt = 45
11.6.2 Flying with air resistance
Next, let’s add air resistance (drag) to this simulation. In the skydiver scenario, we
estimated that the drag constant was 0.2, but that was based on the assumption
that the skydiver is falling flat. A human cannonball, flying head-first, probably
has a drag constant closer to 0.1. Let’s build-up our code to ultimately solve for
the initial velocity required to achieve the record flight distance of 61.06 m.
Before jumping in, I have admittedly been lax in my presentation of the
drag equation. In the skydiver scenario, we had a one-dimensional trajectory
where the y position was always decreasing with time. While gravity always acts
in the negative y direction, drag acts opposite to motion. So in the skydiver
scenario, drag always acted in the positive y direction. What about our projectile
problem? If we consider our trajectory, for x, motion will always be in the positive
x direction. However for y, motion will initially be in the positive y direction,
then go through an extremum, and the be in the negative y direction. The more
proper way to express the force due to drag is:
F drag = −c v 2 v̂
where v̂ is a unit vector that gives the direction of motion, so that −v̂ has the
effect of having F drag act in the opposite direction of the motion. Computing v̂
using MATLAB is straightforward using the built-in sign function.
Let’s start by updating projectile_model_2 (Listing 11.21) to include drag.
We will assume our human cannonball has the same mass as our skydiver, 75 kg.
To include the effect of drag, we need just update the acceleration function. I
will also update the code to return only the distance traveled. I will name this
updated code projectile_resistance.
420
Chapter 11 Second-order systems
Listing 11.23 projectile_resistance.m
1
function res = projectile_resistance()
2
3
4
5
% The initial position in m
x0 = 0;
y0 = 3;
6
7
8
9
% The initial velocity in m/s and launch angle in degrees
v0 = 60;
theta0 = 45;
10
11
12
13
% Computing the x and y component of the initial velocity
vx0 = v0*cosd(theta0);
vy0 = v0*sind(theta0);
14
15
16
17
% The initial time. We will solve for the final time as the time
% required to make contact, y=0. (This criteria can also be updated.)
t0 = 0;
18
19
20
21
22
23
24
25
26
% Solving for the time required to make contact using fzero. The error
% function will be nested. The final time fzero calls the error
% function will correspond to when t is when contact is made. By
% nesting we will not need to resolve at this time. The error function
% is just solving the ODEs over the rante t0 to t, and returning the y
% position at t.
M = []; % Creating variable M without assign a value.
t_impact = fzero(@projectile_solve,[0.1,100]);
27
28
29
30
31
32
33
34
% Let's extract X and Y from M to facilitate plotting. We will also
% return the distance traveled.
X = M(:,1);
Y = M(:,2);
%VX = M(:,3);
%VY = M(:,4);
res = X(end);
35
36
37
38
39
40
41
42
43
% Next, let's plot the trajectory in the xy plane
figure(1)
plot(X,Y,'-k')
xlabel('x position [m]')
ylabel('y position [m]')
title('Trajectory in xy plan')
print('-depsc','xy_trajectory.eps')
11.6 Two dimensions
function res = projectile_solve(t)
% Solving our ODE over the range t0 to t
[T,M] = ode45(@projectile,[t0,t],[x0,y0,vx0,vy0]);
44
45
46
47
48
49
end
50
% Returning the y position at t
res = M(end,2);
51
52
end
53
54
55
56
function res = projectile(t, W)
P = W(1:2);
V = W(3:4);
57
dPdt = V;
dVdt = acceleration(t, P, V);
58
59
60
61
62
end
res = [dPdt; dVdt];
63
64
65
66
67
68
69
70
71
72
function res = acceleration(t, P, V)
g = 9.8;
% acceleration of gravity in m/s^2
%
c = 0.1;
% drag constant
m = 75;
% mass in kg
Vhat = sign(V);
% unit vector giving direction of velocity
% componetns
f_drag = -c * V.^(2) .* Vhat; % drag force in N
a_drag = f_drag ./ m; % drag acceleration in m/s^2
73
% Return as vector acceleration in x and y direction.
% In the x direction is just drag. In the y direction it is drag - g.
res = [a_drag(1); a_drag(2)-g];
74
75
76
77
78
end
>> x_dist = projectile_resistance()
x_dist = 287.7751
421
422
Chapter 11 Second-order systems
Trajectory in xy plan
90
80
70
y position [m]
60
50
40
30
20
10
0
-10
0
50
100
150
200
250
300
x position [m]
Figure 11.13 projectile_resistance()
We find that including air resistance, the distance traveled is shorter. The distance
traveled is almost 288 m, which tells me the initially velocity is... very large
considering the world record is about 61 m.
Next, let’s solve for the initial velocity required to achieve the world record
of 61.06 m traveled. To do this, I will start by keeping the initial launch angle
fixed at 45 degrees and add the initial velocity as an input variable. This gives us a
function where we pass the initial velocity and it returns the distance traveled.
11.6 Two dimensions
Listing 11.24 projectile_resistance_2.m
1
function res = projectile_resistance_2(v0)
2
3
4
5
% The initial position in m
x0 = 0;
y0 = 3;
6
7
8
9
% The initial velocity in m/s and launch angle in degrees
%v0 = 60;
theta0 = 45;
10
11
12
13
% Computing the x and y component of the initial velocity
vx0 = v0*cosd(theta0);
vy0 = v0*sind(theta0);
14
15
16
17
% The initial time. We will solve for the final time as the time
% required to make contact, y=0. (This criteria can also be updated.)
t0 = 0;
18
19
20
21
22
23
24
25
26
% Solving for the time required to make contact using fzero. The error
% function will be nested. The final time fzero calls the error
% function will correspond to when t is when contact is made. By
% nesting we will not need to resolve at this time. The error function
% is just solving the ODEs over the rante t0 to t, and returning the y
% position at t.
M = []; % Creating variable M without assign a value.
t_impact = fzero(@projectile_solve,[0.1,100]);
27
28
29
30
31
32
33
34
% Let's extract X and Y from M to facilitate plotting. We will also
% return the distance traveled.
X = M(:,1);
Y = M(:,2);
%VX = M(:,3);
%VY = M(:,4);
res = X(end);
35
36
37
38
39
40
41
42
43
% Next, let's plot the trajectory in the xy plane
figure(1)
plot(X,Y,'-k')
xlabel('x position [m]')
ylabel('y position [m]')
title('Trajectory in xy plan')
print('-depsc','xy_trajectory.eps')
423
424
Chapter 11 Second-order systems
function res = projectile_solve(t)
% Solving our ODE over the range t0 to t
[T,M] = ode45(@projectile,[t0,t],[x0,y0,vx0,vy0]);
44
45
46
47
48
49
end
50
% Returning the y position at t
res = M(end,2);
51
52
end
53
54
55
56
function res = projectile(t, W)
P = W(1:2);
V = W(3:4);
57
dPdt = V;
dVdt = acceleration(t, P, V);
58
59
60
61
62
end
res = [dPdt; dVdt];
63
64
65
66
67
68
69
70
71
72
function res = acceleration(t, P, V)
g = 9.8;
% acceleration of gravity in m/s^2
%
c = 0.1;
% drag constant
m = 75;
% mass in kg
Vhat = sign(V);
% unit vector giving direction of velocity
% componetns
f_drag = -c * V.^(2) .* Vhat; % drag force in N
a_drag = f_drag ./ m; % drag acceleration in m/s^2
73
% Return as vector acceleration in x and y direction.
% In the x direction is just drag. In the y direction it is drag - g.
res = [a_drag(1); a_drag(2)-g];
74
75
76
77
78
end
I could keep updating the code, but it is also easy enough to solve in the Command Window.
>> projectile_error = @(v) projectile_model_resistance_2(v) - 61.06;
>> vmin = fzero(projectile_error,[0.1,60])
vmin = 24.5105
The minimum velocity required is 24.5105 m/s; that’s about 54.8 mph. You will
11.6 Two dimensions
notice that each time fzero calls projectile_model_resistance_2 it generates a plot. We can avoid this using the following updated code:
Listing 11.25 projectile_resistance_3.m
1
function res = projectile_resistance_3()
2
3
4
5
% Creating variables without assiging an initial value
X = [];
Y = [];
6
7
res = fzero(@projectile_error,[0.1,60]);
8
9
10
11
12
13
14
15
% Next, let's plot the trajectory in the xy plane
figure(1)
plot(X,Y,'-k')
xlabel('x position [m]')
ylabel('y position [m]')
title('Trajectory in xy plan')
print('-depsc','xy_trajectory.eps')
16
17
function res = projectile_error(v0)
18
19
20
21
% The initial position in m
x0 = 0;
y0 = 3;
22
23
24
25
% The initial velocity in m/s and launch angle in degrees
%v0 = 60;
theta0 = 45;
26
27
28
29
% Computing the x and y component of the initial velocity
vx0 = v0*cosd(theta0);
vy0 = v0*sind(theta0);
30
31
32
33
% The initial time. We will solve for the final time as the time
% required to make contact, y=0. (This criteria can also be updated.)
t0 = 0;
34
35
36
37
38
39
40
%
%
%
%
%
%
Solving for the time required to make contact using fzero. The error
function will be nested. The final time fzero calls the error
function will correspond to when t is when contact is made. By
nesting we will not need to resolve at this time. The error function
is just solving the ODEs over the rante t0 to t, and returning the y
position at t.
425
426
Chapter 11 Second-order systems
M = []; % Creating variable M without assign a value.
t_impact = fzero(@projectile_solve,[0.1,100]);
41
42
43
% Let's extract X and Y from M to facilitate plotting.
X = M(:,1);
Y = M(:,2);
VX = M(:,3);
VY = M(:,4);
44
45
46
47
48
49
% return the distance traveled - 61.06 m, the world record
res = X(end) - 61.06;
50
51
52
function res = projectile_solve(t)
% Solving our ODE over the range t0 to t
[T,M] = ode45(@projectile,[t0,t],[x0,y0,vx0,vy0]);
53
54
55
56
57
58
end
59
% Returning the y position at t
res = M(end,2);
60
end
61
62
63
end
64
65
66
67
function res = projectile(t, W)
P = W(1:2);
V = W(3:4);
68
dPdt = V;
dVdt = acceleration(t, P, V);
69
70
71
72
73
end
res = [dPdt; dVdt];
74
75
76
77
78
79
80
81
82
83
84
function res = acceleration(t, P, V)
g = 9.8;
% acceleration of gravity in m/s^2
%
c = 0.1;
% drag constant
m = 75;
% mass in kg
Vhat = sign(V);
% unit vector giving direction of velocity
% componetns
f_drag = -c * V.^(2) .* Vhat; % drag force in N
a_drag = f_drag ./ m; % drag acceleration in m/s^2
11.7 What could go wrong?
427
% Return as vector acceleration in x and y direction.
% In the x direction is just drag. In the y direction it is drag - g.
res = [a_drag(1); a_drag(2)-g];
85
86
87
88
89
end
>> vmin = projectile_resistance_3()
vmin = 24.5105
Where here only a single plot is generated with our final answer. Cool!
The minimum velocity of 54.8 mph is less than the velocity suggested by
Wikipedia of over 70 mph (approximately 31.3 m/s). Likely my estimate of c was
off. But that was a fun problem nonetheless.
11.7 What could go wrong?
What could go wrong? Well, vertcat for one. To explain what that means, I’ll
start with catenation, which is the operation of joining two matrices into a larger
matrix. “Vertical catenation” joins the matrices by stacking them on top of each
other; “horizontal catenation” lays them side by side.
Here’s an example of horizontal catenation with row vectors:
>> x = 1:3
x = 1
2
3
>> y = 4:5
y = 4
5
>> z = [x, y]
z = 1
2
3
4
5
Inside brackets, the comma operator performs horizontal catenation. The vertical
catenation operator is the semi-colon. Here is an example with matrices:
>> X = zeros(2,3)
X = 0
0
0
0
0
0
>> Y = ones(2,3)
Y = 1
1
1
1
1
1
>> Z = [X; Y]
428
Chapter 11 Second-order systems
Z =
0
0
1
1
0
0
1
1
0
0
1
1
These operations only work if the matrices are the same size along the dimension where they are glued together. If not, you get:
>> a = 1:3
a = 1
2
3
>> b = a'
b = 1
2
3
>> c = [a, b]
Error using horzcat
Dimensions of matrices being concatenated are not consistent.
>> c = [a; b]
Error using vertcat
Dimensions of matrices being concatenated are not consistent.
In this example, a is a row vector and b is a column vector, so they can’t be
catenated in either direction.
Reading the error messages, you probably guessed that horzcat is the function that performs horizontal catenation, and likewise with vertcat and vertical
catenation.
These operations are relevant to projectile because of the last line, which
packs dPdt and dVdt into the output variable:
1
2
3
function res = projectile(t, W)
P = W(1:2);
V = W(3:4);
4
dPdt = V;
dVdt = acceleration(t, P, V);
5
6
7
8
9
end
res = [dPdt; dVdt];
As long as both dPdt and dVdt are column vectors, the semi-colon performs
vertical catenation, and the result is a column vector with four elements. But
11.8 ODE Events
if either of them is a row vector, that’s trouble. ode45 expects the result from
projectile to be a column vector, so if you are working with ode45, it is probably
a good idea to make everything a column vector.
In general, if you run into problems with horzcat and vertcat, use size to
display the dimensions of the operands, and make sure you are clear on which
way your vectors go.
11.8 ODE Events
Normally when you call ode45 you have to specify a start time and an end time.
But in many cases, you don’t know ahead of time when the simulation should end.
We have seen now how we can deal with this using fzero. Additionally, MATLAB
provides a mechanism for dealing with this problem directly using ode45. The
bad news is that it is a little awkward. Here’s how it works:
1. Before calling ode45 you use odeset to create an object called options
that contains values that control how ode45 works:
>> options = odeset('Events', @event_function);
In this case, the name of the option is Events and the value is a function handle. When ode45 runs, it will invoke event_function after each
timestep. You can call this function anything you want, but the name
event_function helps eliminate confusion. If you wish to define multiple
Events in a single code, then you might expand the name to prevent mixing
them up (i.e., event_function_projectile).
2. The function you provide has to take the same input variables as your rate
function. For example, here is an event function that would work with
projectile (Listing 11.18) from Section 11.6 to find the time when the
projectile hits the ground:
429
430
Chapter 11 Second-order systems
Listing 11.26 event_function.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
t Remember, you do not need
to unpack W at all. It is a
habit I tend to do to eliminate any confusion. You
could just as well use value
= W(2).
% When value is equal to zero, an event is triggered.
%
% Set isterminal to 1 to stop the solver at the first event,
% or 0 to get all events.
%
% direction = 0 if all zeros are to be computed (the default),
% +1 if only zeros where the event function is increasing,
% and -1 if only zeros where the event function is decreasing.
%
function [value,isterminal,direction] = event_function(t,W)
py = W(2);
% Extract the current height.
value = py;
% When value = 0, an event is triggered
isterminal = 1;
% Stop the integration if height crosses zero.
% (Terminate after first event.)
direction = -1;
% But only if the height is decreasing.
end
t is the time, and W is a vector of the value of our functions at t. For
projectile, this is the position in the x and y direction, and the velocity
in the x and y direction, respectively. Here I unpack just the height since
we don’t use the others.
events returns three output variables:
a) value determines when an event occurs. In this case value gets the second element of W, which is understood to be the height of the projectile.
An “event” is a point in time when this value passes through 0.
b) direction determines whether an event occurs when value is increasing (direction=1), decreasing (direction=-1), or either (direction=0).
c) isterminal determines what happens when an event occurs. If isterminal=1,
the event is “terminal” and the simulation stops. If isterminal=0, the
simulation continues, but ode45 does some additional work to make
sure that the solution in the vicinity of the event is accurate, and that
one of the estimated values in the result is at the time of the event.
3. When you call ode45, you pass options as a fourth argument:
>> ode45(@projectile, [0,10], [0, 3, 40, 30], options);
As before, calling ode45 in this manner results in a plot:
11.8 ODE Events
431
250
200
150
100
50
0
-50
0
1
2
3
4
5
6
7
Figure 11.14 ode45(@projectile,[0,10],[0,3,40,30],options)
If you wished to save the result to variables, the command would be:
>> [T,M,TE,ME] = ode45(@projectile, [0,10], [0, 3, 40, 30], options);
Notice the two extra outputs. TE is the time where the event occurred, and
is a scalar for this case; if more than one event occurs, then it will be a vector.
ME is the value of the functions when the event occurred. Here it will be a
row vector of length 4, where the first element is x, second element is y,
third element if v x , and fourth element is v y :
>> TE
TE = 6.2209
>> ME
ME = 248.8347
0
40.0000
-30.9645
This is in exact agreement with our previous result using fzero. If more
than one event occurs, ME will be a matrix, where each row corresponds to
an the event at the corresponding time in TE. (Think of the relation between
T and M, only here we consider only times when events occur.)
If the use of fzero and Events give the same result, why should I consider
using Events? Remember, when using fzero we solved our ODEs for several values of t until our root was found. The use of Events is much less
computationally demanding. While not noticeable here, it may make a
432
Chapter 11 Second-order systems
noticeable difference if it will be used many times in a code. However, I do
find sometimes that with the default settings, the numerical accuracy of
Events is less than fzero. Typically this is not significant for our purposes.
But you can use odeset to update the tolerance used by ode45 to overcome
this. You might try something like:
>> options = odeset('Events',@event_function,'RelTol',1e-8,'AbsTol', 1eFor additional information, the MATLAB documentation provides two help
pages I would suggest you review: “ODE Event Location” and “Summary of
ODE Options”.
Example 11.3
How would you modify events to stop when the height of the projectile falls
through 3 m?
Solution: Similar to when using fzero, an event will be triggered when value
is equal to zero. So to find when the height is equal to 3 m, we will subtract 3
from the current value of the height. Additionally, notice that the problem asks
when the height falls through 3 m. I will take this to refer to when the projectile is
descending. We will therefor use a direction of –1.
Listing 11.27 event_function_y3.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% When value is equal to zero, an event is triggered.
%
% Set isterminal to 1 to stop the solver at the first event,
% or 0 to get all events.
%
% direction = 0 if all zeros are to be computed (the default),
% +1 if only zeros where the event function is increasing,
% and -1 if only zeros where the event function is decreasing.
%
function [value,isterminal,direction] = event_function_y3(t,W)
py = W(2);
% Exctract the current height.
value = py-3;
% Desire to know when height is 3 m.
% When value = 0, an event is triggered
isterminal = 1;
% Stop the integration if height crosses zero.
% (Terminate after first event.)
direction = -1;
% But only if the height is decreasing.
end
And now to execute and find then print the height and the time to reach a height
of 3 m:
11.8 ODE Events
433
>> options = odeset('Events', @event_function_y3);
>> [T,M,TE,ME] = ode45(@projectile, [0,10], [0, 3, 40, 30], options);
>> ME(2)
ans = 3.0000
>> TE
ans = 6.1224
Example 11.4
Multiple Events. Find the time when the height of the projectile falls through 3 m
and when it makes impact with the ground. (The integration should be stopped
upon impact.) Plot the trajectory and label when the height falls through 3 m.
Solution: Let’s start by updating our event_function_y3 from the last example.
Since we have two events, our variables will all be row vectors of length 2. For
isterminal, for the first event we will indicate to continue the integration, and
then stop at the second event.
Listing 11.28 event_function_y30.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% When value is equal to zero, and event is triggered.
%
% Set isterminal to 1 to stop the solver at the first event,
% or 0 to get all events.
%
% direction = 0 if all zeros are to be computed (the default),
% +1 if only zeros where the event function is increasing,
% and -1 if only zeros where the event function is decreasing.
%
function [value,isterminal,direction] = event_function_y30(t,W)
py = W(2);
% Extract the current height.
value = [py-3,py];
% Event happens when the height is 3 m and 0 m.
isterminal = [0,1];
% Continue after the first event, stop after the second.
direction = [-1,-1];
% But only if the height is decreasing.
end
Now to run and obtain the desired information. Since we will have two events, TE
and ME contain additional information. I will print the values and then and see if
you can interpret the results:
434
Chapter 11 Second-order systems
>> options = odeset('Events', @event_function_y30);
>> [T,M,TE,ME] = ode45(@projectile, [0,10], [0, 3, 40, 30], options);
>> TE
TE = 6.1224
6.2209
>> ME
ME =
244.8980
248.8347
3.0000
0
40.0000
40.0000
-30.0000
-30.9645
TE is now a column vector with two rows. The first entry is the time of the first
event, the second entry is the time of the second event. For ME, we now have two
rows. The first row corresponds to the value of the functions at the first event, and
the second row corresponds to the value of the functions at the second event.
Now let’s plot the trajectory and our first event:
hold on
plot(M(:,1),M(:,2),'-r',ME(1,1),ME(1,2),'bo')
xlabel('x [m]')
ylabel('y [m]')
50
45
40
35
30
y [m]
>>
>>
>>
>>
25
20
15
10
5
0
0
50
100
150
200
x [m]
Figure 11.15 ode45(@projectile,[0,10],[0,3,40,30],options)
250
11.8 ODE Events
435
Example 11.5
Finding the maximum in the trajectory. Find the time when the height of the projectile is a maximum and when it makes impact with the ground. (The integration
should be stopped upon impact.) Plot the trajectory and label the maximum.
Solution: As compared to the last problem, here we are asked to find a maximum
in the trajectory. This will correspond to the point where d y/d t = 0, and is then
decreasing after the event. (To the right of the top of the hill we are going down.
Note if we were looking for a minimum the opposite would be the case.) For our
problem d y/d t = v y .
Listing 11.29 event_function_max.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
% When value is equal to zero, and event is triggered.
%
% Set isterminal to 1 to stop the solver at the first event,
% or 0 to get all events.
%
% direction = 0 if all zeros are to be computed (the default),
% +1 if only zeros where the event function is increasing,
% and -1 if only zeros where the event function is decreasing.
%
function [value,isterminal,direction] = event_function_max(t,W)
py = W(2);
% Extract the current height
vy = W(4);
% Extract the current y velocity
value = [vy,py];
% Event happens when the y velocity and height are zero
isterminal = [0,1];
% Continue after the first event, stop after the second.
direction = [-1,-1];
% But only if the velocity and height are decreasing.
end
>> options = odeset('Events', @event_function_max);
>> [T,M,TE,ME] = ode45(@projectile, [0,10], [0, 3, 40, 30], options);
>> TE
TE = 3.0612
6.2209
>> ME
ME =
122.4490
248.8347
48.9184
0
40.0000
40.0000
-0.0000
-30.9645
So the maximum occurs at 3.0612 seconds, and the maximum height is 48.9184 m.
Plotting:
>>
>>
>>
>>
hold on
plot(M(:,1),M(:,2),'-r',ME(1,1),ME(1,2),'bo')
xlabel('x [m]')
ylabel('y [m]')
436
Chapter 11 Second-order systems
50
45
40
35
y [m]
30
25
20
15
10
5
0
0
50
100
150
200
250
x [m]
Figure 11.16 ode45(@projectile,[0,10],[0,3,40,30],options)
Example 11.6
Minimum and Maximum. Let’s revisit the Lotka-Voltera population model of
Section 10.1. We used this predator and pray model to model the population of
rabbits and foxes. Solving or a period of 1 year (or 365 days), we found that the
rabbit and fox population went through a series of minimums and maximums.
Find the time when the population of rabbits and foxes experiences a minimum
and maximum. Plot these points along with the population over the course of the
year.
Solution: Let’s start by writing a function for Events. Since we are looking for
minimum and maximum, we will need to find where the first derivative is zero.
How do we get the first derivative? Use our rate function! How do we identify
maximum versus minimum? For a maximum, after the maximum the function is
decreasing. For minimum, after the minimum the function is increasing.
11.8 ODE Events
437
Listing 11.30 event_function_lotka.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
% When value is equal to zero, and event is triggered.
%
% Set isterminal to 1 to stop the solver at the first event,
% or 0 to get all events.
%
% direction = 0 if all zeros are to be computed (the default),
% +1 if only zeros where the event function is increasing,
% and -1 if only zeros where the event function is decreasing.
%
function [value,isterminal,direction] = event_function_lotka(t,X)
% Find the rate of change of r and f from the rate function,
% and unpack the result
rate = lotka(t,X);
drdt = rate(1);
dfdt = rate(2);
% rabbit max and min, followed by fox max then min
% For all events we continue the integration. For this problem
% we know the stopping time is 365 days.
value = [drdt,drdt,dfdt,dfdt];
isterminal = [0,0,0,0];
direction = [-1,1,-1,1];
end
And now solving. To distinguish our events, we now add an extra output variable
IE. IE will be a column vector of the same length of T, which indicates which event
has occurred (here 1, 2, 3, or 4) at each time. The order of the events agrees with
value, isterminal, and direction.
>>
>>
>>
IE
options = odeset('Events', @event_function_lotka);
[T,M,TE,VE,IE] = ode45(@lotka, [0,365], [100,10], options);
IE
=
1
3
2
4
1
3
2
4
1
3
2
4
1
3
2
4
1
3
438
Chapter 11 Second-order systems
2
4
1
3
2
>> TE
TE =
0.0000
11.4774
27.6459
49.9308
66.1076
77.5703
93.7310
116.0370
132.2143
143.6729
159.8410
182.1584
198.3378
209.7896
225.9572
248.2904
264.4706
275.9181
292.0897
314.4344
330.6162
342.0582
358.2304
Nice! It would be great if we could isolate occurrences for each event... We can do
that too! Remember logical statements as applied to vectors.
>> IE==1
ans =
1
0
0
0
1
0
0
0
1
0
0
0
1
11.8 ODE Events
0
0
0
1
0
0
0
1
0
0
The result is a vectors of 1’s and 0’s. 1 correspond to true, 0 false. This tells us which
events correspond to event 1 and which do not. We can pass this information to
our vector TE to find all times for which event 1 occurs:
>> TE(IE==1)
ans =
0.0000
66.1076
132.2143
198.3378
264.4706
330.6162
The same could be done for the other events, but we will not do so here in the
interest of space. Now let us plot the results of the number of rabbits and foxes
versus time, and label the maximums and minimums. I will use a different color
symbol for the maximum and minimum of rabbits and foxes. I will start by separating M and VE into two separate vectors, one for the population of rabbits, the
other for the population of foxes.
>>
>>
>>
>>
>>
>>
RAB = M(:,1);
FOX = M(:,2);
RABE = VE(:,1);
FOXE = VE(:,2);
hold on
plot(T,RAB,'-r',T,FOX,'-b',TE(IE==1),RABE(IE==1),'ro',
TE(IE==2),RABE(IE==2),'rx',TE(IE==3),FOXE(IE==3),'bo',
TE(IE==4),FOXE(IE==4),'bx')
>> xlabel('time [days]')
>> ylabel('population')
439
440
Chapter 11 Second-order systems
120
100
population
80
60
40
20
0
0
50
100
150
200
250
300
350
400
time [days]
Figure 11.17 Max and mins in our predator and prey model.
Note that in the previous two examples we knew what the order of the events
would be. I.e., the projectile would go through a maximum before hitting the
ground. But there too IE could be very useful in identifying events as part of a
larger program, and it can help reduce mistakes.
11.9 What could go wrong?
The MATLAB documentation lists three limitations of the use of ODE Events:
1. If a terminal event occurs during the first step of the integration, then the
solver registers the event as nonterminal and continues integrating.
2. If more than one terminal event occurs during the first step, then only the
first event registers and the solver continues integrating.
3. Zeros are determined by sign crossings between steps. Therefore, zeros of
functions with an even number of crossings between steps can be missed.
I do not expect this to cause any issues in this class, but I thought it good to
mention them. If you encounter an issue, you can look at the documentation
for additional options to increase the accuracy of the integration or decrease the
maximum step size.
11.10 Glossary
11.10 Glossary
parallel functions: Two or more functions defined side-by-side, so that one ends
before the next begins.
nested function: A function defined inside another function.
outer function: A function that contains another function definition.
inner function: A function defined inside another function definition. The inner
function can access the variables of the outer function.
catenation: The operation of joining two matrices end-to-end to form a new
matrix.
11.11 Exercises
Exercise 11.1 For our human cannonball problem, we found that the optimal launch
angle in vacuum was 45 degrees (see Listing 11.22). Let’s build-up this code. In your
solution, use fzero unless told otherwise.
1. Update your code to account for air resistance (drag). What is the optimal launch
angle with air resistance? You should find that it does not change much.
2. The acceleration due to drag takes the form a drag = F drag /m. We therefore expect
that as mass decreases, the effect of drag increases. Compute the optimal launch
angle over the range 0.04593 kg (mass of a golf ball) to 75 kg (the mass of our
human cannonball). Plot the optimal launch angle versus mass and comment on
your findings.
3. Similar to the last exercise, F drag is proportional to the velocity squared. How
does the initial velocity effect the optimal launch angle? Consider the case of a
golf ball (0.04593 kg) and our human cannonball (75 kg). Compute the optimal
launch angle over a range of velocities from 5 m/s (about 11 mph) to 50 m/s (about
112 mph). Plot the optimal launch angle versus initial velocity for each case and
comment on your findings.
4. Last, repeat your calculations using using ODE Events in place of fzero. Comment
on the improved efficiency (and maybe readability) of your code.
If you want to compare the exact execution time of the calculation using fzero
and ODE Events you can use the built-in MATLAB function timeit. Imagine my
function is called test and takes no input variables. Then to run and time from
the Command Window, I would call the function as timeit(test). It will return
the execution time in seconds. Note that it performs the calculation several times
to get an average execution time, so be patient if it is taking a little longer than
expected.
Due to the numerical precision of ODE Events, your results may differ from those
obtained using fzero. They should be close, but not exactly the same. Comment
on any differences observed. Know that it is possible to update the default settings
and improve the accuracy of the ODE Events calculations. An example would be:
441
442
Chapter 11 Second-order systems
>> options = odeset('Events',@event_function,'RelTol',1e-8,'AbsTol', 1e-8)};
12
Chapter
Interpolation
In this chapter we will learn about interpolating with MATLAB. By the end of this
chapter you will be able to:
• Demonstrate use of interp1 to interpolate with respect to a single independent variable
• Apply interp1 to created an automated steam table
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
12.1 Discrete and continuous maps
When you solve an ODE analytically, the result is a function, which you can think
of as a continuous map. When you use an ODE solver, you get two vectors (or a
vector and a matrix), which you can think of as a discrete map.
For example, in Section 9.4, we used the following rate function to estimate
the population of rats as a function of time, Listing 9.1:
1
2
3
4
5
function res = rats(t, y)
a = 0.01;
omega = 2 * pi / 365;
res = a * y * (1 + sin(omega * t));
end
The result from ode45 is two vectors:
>> [T, Y] = ode45(@rats, [0, 365], 2);
T contains the time values where ode45 estimated the population; Y contains the
population estimates.
443
444
Chapter 12 Interpolation
Now suppose we would like to know the population on the 180th day of the
year. We could search T for the value 180:
>> find(T==180)
ans = Empty matrix: 0-by-1
But there is no guarantee that any particular value appears in T. We can find the
index where T crosses 180:
>> I = find(T>180); I(1)
ans = 23
I gets the indices of all elements of T greater than 180, so I(1) is the index of the
first one.
Then we find the corresponding value from Y:
>> [T(23), Y(23)]
ans = 184.3451
40.3742
That gives us a coarse estimate of the population on Day 180. If we wanted to do
a little better, we could also find the last value before Day 180:
>> [T(22), Y(22)]
ans = 175.2201
36.6973
So the population on Day 180 was between 36.6973 and 40.3742.
But where in this range is the best estimate? A simple option is to choose
whichever time value is closer to 180 and use the corresponding population
estimate. In the example, that’s not a great choice because the time value we want
is right in the middle.
So far we have seen two techniques to overcome this limitation.
1. We can create a function to solve the differential equation over the range 0
to t , and return the value at t . For this problem we would pass t = 180 and
the function would return the exact population at 180 days.
2. We can use ODE Events. Here we would create an event for t = 180 (or
value = t-180). In this chapter we will learn yet another technique, interpolation. Interpolating is a broadly applicable technique, not just limited
to differential equations.
12.2 Interpolation
t Here we will use interp1
for interpolating in 1-D.
interp2, interp3, and
interpn also exist to interpolate in 2-D, 3-D, and N-D,
respectfully.
A better option is to draw a straight line between the two points that bracket Day
180 and use the line to estimate the value in between. This process is called linear
12.2 Interpolation
445
interpolation, and MATLAB provides a function named interp1 that does it:
>> pop = interp1(T, Y, 180)
pop = 38.6233
The first two arguments specify a discrete map from the values in T to the values
in Y. The third argument is the time value where we want to interpolate. The
result is what we expected, about halfway between the values that bracket it.
interp1 can also take a fourth argument that specifies what kind of interpolation you want. The default is ’linear’, which does linear interpolation. Other
choices include ’spline’ which uses a spline curve to fit two points on either
side, and ’pchip’, which uses piecewise cubic Hermite interpolation. (Note
that ’pchip’ was formerly ’cubic’). In addition to the MATLAB documentation,
there is an interesting MATLAB blog page titled: “Splines and Pchips” that you
may find interesting.
>> pop = interp1(T, Y, 180, 'spline')
pop = 38.6486
>> pop = interp1(T, Y, 180, 'pchip')
pop = 38.6491
In this case we expect the spline and cubic interpolations to be better than linear,
because they use more of the data, and we know the function isn’t linear. But we
have no reason to expect the spline to be more accurate than the cubic, or the
other way around. Fortunately, they are not very different.
We can also use interp1 to project the rat population out beyond the values
in T:
>> [T(end), Y(end)]
ans = 365.0000
76.9530
>> pop = interp1(T, Y, 370, 'pchip')
pop = 80.9971
This process is called extrapolation. For time values near 365, extrapolation may
be reasonable, but as we go farther into the “future,” we expect them to be less
accurate. For example, here is the estimate we get by extrapolating for a whole
year:
>> pop = interp1(T, Y, 365*2, 'pchip')
pop = -4.8879e+03
And that’s wrong. So very wrong.
t Note that if you use
’cubic’ you will get a warning that the name will be
discontinued in future releases of MATLAB. MATLAB instead wants you to
use the keyword ’pchip’,
which they say does the
same thing.
t Note that MATLAB has
built-in functions spline
and pchip for interpolating
with these specific methods. I will exclusively use
interp1 as it is more general an gives us greater
flexibility.
446
Chapter 12 Interpolation
Here we have passed the optional argument pchip for our interpolation
method, which we in turn used to extrapolate. What if we didn’t list a method so
as to use the default, and then try to extrapolate? Let’s give it a try:
>> pop = interp1(T, Y, 365*2)
pop = NaN
>> pop = interp1(T, Y, 400)
pop = NaN
What gives? If four input parameters are provided, as we did by specifying the
method, and the method is either pchip (or cubic) or spline, MATLAB will allow
you to perform extrapolation. For all other cases (i.e., linear), you will need to
pass an additional optional argument, ’extrap’, to perform an extrapolation. In
your input list, ’extrap’ would follow the method used. If you wish to extrapolate using linear interpolation, you will need to specify the method as ’linear’
followed by ’extrap’. If the method is pchip (or cubic) or spline, then you do
not need to list it if you do not want to, it is the default setting. Let’s try again to
extrapolate with linear interpolation:
>> pop = interp1(T, Y, 365*2,'linear','extrap')
pop = 342.6316
>> pop = interp1(T, Y, 400,'linear','extrap')
pop = 102.4291
Example 12.1
The Dortmund Data Bank (DDBST) has made the following set of isothermal vapor/liquid equilibrium data freely availabe for the binary system acetone(1)/benzene(2)
at 318.15 K. 1
(a) Using the provided data, estimate the liquid (x 1 ) and vapor (y 1 ) mole fracs
at 55 kPa using interp1.
(b) Use interp1 to estimate values of the pressure (p) and y 1 over the range
0 ≤ x 1 ≤ 1, and plot the results.
1 http://www.ddbst.com/en/EED/VLE/VLE%20Acetone%3BBenzene.php
12.2 Interpolation
447
p (kPa)
33.428
36.666
43.230
46.450
50.647
53.293
57.722
60.527
63.380
66.037
67.189
x1
0.04700
0.09630
0.22070
0.29360
0.40110
0.47590
0.61250
0.70450
0.80810
0.90840
0.95290
y1
0.14440
0.25740
0.44170
0.52040
0.61390
0.66970
0.76140
0.82010
0.88050
0.94180
0.96990
Solution: (Link to screen cast and accompanying M-file.)
Let’s do it! I will start by created vectors for the reference data.
>> Pref = [33.428,36.666,43.230,46.450,50.647,53.293,57.722,60.527,...
63.380,66.037,67.189];
>> Xref = [0.04700,0.09630,0.22070,0.29360,0.40110,0.47590,0.61250,...
0.70450,0.80810,0.90840,0.95290];
>> Yref = [0.14440,0.25740,0.44170,0.52040,0.61390,0.66970,0.76140,...
0.82010,0.88050,0.94180,0.96990];
Note that while here I enter the data and perform all of the calculations in the
Command Window for illustrative purposes, a script would likely be a better idea.
Next, let’s estimate x 1 and y 1 at p = 55 kPa.
>> x_linear = interp1(Pref,Xref,55)
x_linear = 0.5285
>> y_linear = interp1(Pref,Yref,55)
y_linear = 0.7050
>> x_pchip = interp1(Pref,Xref,55,'pchip')
x_pchip = 0.5272
>> y_pchip = interp1(Pref,Yref,55,'pchip')
y_pchip = 0.7051
We find the predictions using linear interpolation and pchip are in excellent agreement. Next, let’s make a plot.
>>
>>
>>
>>
>>
Xeval = linspace(0,1);
Ylinear = interp1(Xref,Yref,Xeval,'linear','extrap');
Plinear = interp1(Xref,Pref,Xeval,'linear','extrap');
Ypchip = interp1(Xref,Yref,Xeval,'pchip');
Ppchip = interp1(Xref,Pref,Xeval,'pchip');
448
Chapter 12 Interpolation
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
hold on
plot(Pref,Xref,'ok')
plot(Pref,Yref,'ok')
plot(Plinear,Xeval,'-r')
plot(Plinear,Ylinear,'-r')
plot(Ppchip,Xeval,'-g')
plot(Ppchip,Ypchip,'-g')
xlabel('x_1 or y_1')
ylabel('p [kPa]')
title('acetone(1)/benzene at 318.15 K')
acetone(1)/benzene at 318.15 K
1
0.9
0.8
0.7
p [kPa]
0.6
0.5
0.4
0.3
0.2
0.1
0
30
35
40
45
50
x 1 or y
55
60
65
70
1
Figure 12.1 pxy phase diagram for acetone(1)/benzene(2) at 318.15 K.
We find that both sets of predictions are again in excellent agreement, and appear
to well represent the data. Note that the fit is not perfect as indicated by the fact
that the lines (bubble and dew) do not meet up in the limit that x 1 → 0.
12.3 Interpolating the inverse function
t Another alternative to using
fzero, although you do
need to be careful about the
accuracy... if it’s important.
And there are issues with
multi-valued functions, as
we will see later. We could
alternatively use interp1
to create a function that will
allow us to efficiently use
fzero.
We have used interp1 to find population as a function of time; by reversing the
roles of T and Y, we can also interpolate time as a function of population. For
example, we might want to know how long it takes the population to reach 20
rats.
>> interp1(Y, T, 20)
ans = 133.4128
12.3 Interpolating the inverse function
449
This use of interp1 might be confusing if you think of the arguments as x and
y. You might find it helpful to think of them as the range and domain of a map
(where the third argument is an element of the range).
The following plot shows f (Y plotted as a function of T) and the inverse of f
(T plotted as a function of Y).
>>
>>
>>
>>
>>
>>
>>
>>
>>
figure
subplot(2,1,1) % First plot in 2 by 1 matrix
plot(T, Y,'-r')
ylabel('Y')
xlabel('T')
subplot(2,1,2) % Second plot in 2 by 1 matrix
plot(Y, T, '-b')
ylabel('T')
xlabel('Y')
80
Y
60
40
20
0
0
50
100
150
200
250
300
350
400
50
60
70
80
T
400
T
300
200
100
0
0
10
20
30
40
Y
Figure 12.2 Y versus T and T versus Y.
In this case we can use interp1 either way because f is a single-valued mapping,
which means that for each value in the domain, there is only one value in the
range that maps to it.
If we reduce the food supply so that the rat population decreases during the
winter, our rate function might look something like this:
450
Chapter 12 Interpolation
Listing 12.1 rats_winter.m
2
3
4
5
function res = rats_winter(t, y)
a = 0.01;
omega = 2 * pi / 365;
res = a * y * (0.5 + sin(omega * t));
end
Solving and plotting:
>>
>>
>>
>>
[T, Y] = ode45(@rats_winter, [0, 365], 2);
plot(T,Y)
xlabel('T')
ylabel('Y')
18
16
14
12
Y
1
10
8
6
4
2
0
50
100
150
200
250
300
350
400
T
Figure 12.3 ode45(@rats_winter, [0, 365], 2)
We can still use interp1 to map from T to Y:
>> interp1(T, Y, 260)
ans = 15.0309
So on Day 260, the population is about 15, but if we ask on what day the population was 15, there are two possible answers, 172.44 and 260.44. If we try to use
interp1, we get the wrong answer:
>> interp1(Y, T, 15)
ans = 196.3833
12.4 Creating a function
451
On Day 196, the population is actually 16.8, so interp1 isn’t even close! The
problem is that T as a function of Y is a multivalued mapping; for some values
in the range there are more than one values in the domain. This causes interp1
to fail. The issue is interp1 expects a single valued functional relationship, and
unfortunately this is not clearly communicated in the documentation. However,
if our function is multivalued but we have an idea of where the solution occurs,
we can specify bounds (or a range) for the search. For example:
>> Trange1 = T<225;
>> Trange2 = T>225;
>> interp1(Y(Trange1), T(Trange1), 15)
ans = 172.4407
>> interp1(Y(Trange2), T(Trange2), 15)
ans = 260.4365
Nice! We will see in the next section how you can use fzero to obtain the same
result.
12.4 Creating a function
In your code you may find it useful or desirable to have a function that interpolates within T and Y or Y and T. This could be desirable for a number of reasons,
such as: you will be interpolating often, wish to use T and Y again, will re-solve
the ODE with different conditions within your code, wish to use other built-in
functions, etc. We can do this quickly using an anonymous functions. Continuing
on from the previous section:
>> yt = @(t) interp1(T,Y,t);
>> yt(260)
ans = 15.0309
If we have a multivalued function, we can use fzero to overcome the limitations of interp1. Let’s revisit the case of finding when Y is equal to 15. For this
case interp1 failed when using the entire data set. I will start by modifying our
function so that it is 0 when Y is equal to 15, and then I will use fplot to get an
estimate of the time.
>> yt = @(t) interp1(T,Y,t)-15;
>> fplot(yt,[0,365])
>> fzero(yt,170)
ans =
172.4407
t From the documentation
for interp1, linear requires atleast 2 points, and
pchip and spline require
alteast 4 points.
452
Chapter 12 Interpolation
>> fzero(yt,260)
ans =
260.4365
2
0
-2
-4
-6
-8
-10
-12
0
50
100
150
200
250
300
350
Figure 12.4 fplot(yt,[0,365])
Nice! We obtain the same value as when we set bounds in our call to interp1.
Both work, which do you find easiest to use? We will see another use of fzero in
the next section.
One issue with creating a function in your code is over time you might forget
the range covered or another user of your program might not know the range. If
you are using the method ’pchip’ or ’spline’, then MATLAB will by default
allow you to extrapolate. For this case you might want to add a check that if the
user is trying to extrapolate, return a default value. A common choice is 0 and
NaN (not a number), which we found is what MATLAB uses when we tried to
extrapolate without specifying a method (i.e, using the default). Following the
specification of the method used, list the value you wish to return if extrapolating.
Let’s take a look at an example. First we will create a function yt and use it to
extrapolate using ’pchip’. Then we will update the function to return a default
value if we extrapolate, and try again.
>> yt = @(t) interp1(T,Y,t,'pchip');
>> yt(260)
ans = 15.0339
>> yt(400)
12.5 Field mice
453
ans = 14.3354
>> yt = @(t) interp1(T,Y,t,'pchip',NaN);
>> yt(260)
ans = 15.0339
>> yt(400)
ans = NaN
12.5 Field mice
As we’ve seen, one use of interpolation is to interpret the results of a numerical
computation; another is to fill in the gaps between discrete measurements.
For example2 , suppose that the population of field mice is governed by this
rate equation:
g (t , y) = a y − b(t )y 1.7
where t is time in months, y is population, a is a parameter that characterizes
population growth in the absence of limitations, and b is a function of time that
characterizes the effect of the food supply on the death rate.
Although b appears in the equation as a continuous function, we might not
know b(t ) for all t . Instead, we might only have discrete measurements:
t
0
1
2
3
4
5
6
7
8
b (t )
0.0070
0.0036
0.0011
0.0001
0.0004
0.0013
0.0028
0.0043
0.0056
If we use ode45 to solve the differential equation, then we don’t get to choose the
values of t where the rate function (and therefore b) gets evaluated. We need to
provide a function that can evaluate b everywhere:
2 This example is adapted from Gerald and Wheatley, Applied Numerical Analysis, Fourth Edition,
Addison-Wesley, 1989.
454
Chapter 12 Interpolation
Listing 12.2 interpolate_b.m
1
2
3
4
5
6
7
8
9
function res = interpolate_b(t)
T = 0:8;
B = [70 36 11 1 4 13 28 43 56] * 1e-4;
% While linear is default, and by default MATLAB will not
% extrapolate with linear interpolation, I explicitly specify it
% here. This way if I change interp1 to use spline or cubic
% interpolation, I need just update the method.
res = interp1(T, B, t,'linear',NaN);
end
Abstractly, this function uses a discrete map to implement a continuous map.
Example 12.2
Write a rate function that uses interpolate_b to evaluate g and then use ode45 to
compute the population of field mice from t = 0 to t = 8 with an initial population
of 100 and a = 0.9.
Then modify interpolate_b to use spline interpolation and run ode45 again to
see how much effect the interpolation has on the results.
Solution: (Link to screen cast and accompanying M-file.)
Let’s do it! I solved both problems simultaneously. I first created another function
file to perform spline interpolation of b:
Listing 12.3 interpolate_b_spline.m
1
2
3
4
5
6
7
8
9
function res = interpolate_b_spline(t)
T = 0:8;
B = [70 36 11 1 4 13 28 43 56] * 1e-4;
% While linear is default, and by default MATLAB will not
% extrapolate with linear interpolation, I explicitly specify it
% here. This way if I change interp1 to use spline or cubic
% interpolation, I need just update the method.
res = interp1(T, B, t,'spline',NaN);
end
Our rate function will contain the population growth parameter a. While we could
explicitly define it in the file, it would be nice if I could pass it as a parameter.
Passing it is not possible since ode45 will expect just two input parameters. I will
therefore write a top function that I can pass a and solve the ODE, then I will nest
the rate function within so that it can see a. Likewise, I will pass to the top function
a flag variable. When flag == 1 we will use linear interpolation for b, otherwise
we will use spline interpolation. By nesting the rate function, it can see the value of
the flag variable. It will then pass this to a modified version of interpolation_b
12.5 Field mice
455
which will perform linear or spline interpolation depending on the value of flag
passed to it.
In addition to solving and returning the result to the Command Window , I will also
plot the results within the function to help visualize whether using spline interpolation makes a difference. Below are my functions followed with their use from the
Command Window and the results.
Listing 12.4 field_mice.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
% function res = field_mice(TSPAN,y0,a,flag)
%
% TSPAN is a two element row vector with elements t0 and tfinal
% y0 is the initial population
% a is the population parameter
% flag will indicate to use linear or spline interpolation
%
if flag == 1, linear
%
else, spline
%
% The function will return a matrix where column 1 is
% time and column 2 is the population.
%
% I will also generate a function of y vs t within the function.
% If we are using linear interpolation, the line will be red,
% and will be blue for spline.
%
function res = field_mice(TSPAN,y0,a,flag)
[T,Y] = ode45(@field_mice_rate,TSPAN,y0);
% Pack T and Y into a matrix and return as result
res = [T,Y];
% Plotting
if flag == 1
plot(T,Y,'-r')
else
plot(T,Y,'-b')
end
xlabel('time [months]')
ylabel('field mice population')
function res = field_mice_rate(t,y)
% Interpolate to find b
if flag == 1
b = interpolate_b(t);
else
b = interpolate_b_spline(t);
end
end
end
res = a*y-b*y^(1.4);
456
Chapter 12 Interpolation
Listing 12.5 interpolate_b_field_mice.m
function res = interpolate_b_field_mice(t,flag)
T = 0:8;
B = [70 36 11 1 4 13 28 43 56] * 1e-4;
% If flag == 1, use linear interpolation, else use spline
if flag == 1
res = interp1(T, B, t,'linear',NaN);
else
res = interp1(T, B, t,'spline',NaN);
end
end
>>
>>
>>
>>
>>
>>
>>
TSPAN = [0,8];
y0 = 100;
a = 0.9;
linear = field_mice(TSPAN,y0,a,1);
hold on
spline = field_mice(TSPAN,y0,a,2);
legend('linear interpolation','spline interpolation')
7
×10 4
linear interpolation
spline interpolation
6
5
field mice population
1
2
3
4
5
6
7
8
9
10
4
3
2
1
0
0
1
2
3
4
5
6
7
8
time [months]
Figure 12.5 Comparison of linear and spline interpolation of b.
Looking at the plot, there does not seem to be much difference on the result when
using linear and spline interpolation of b. From the figures, the growth appears to
be exponential, so let us plot the log of the population versus time. This should
linearize the functions. We will then see if they still appear to be the same.
12.6 Good results come from good users
>>
>>
>>
>>
>>
>>
457
plot(linear(:,1),log(linear(:,2)),'-r')
hold on
plot(spline(:,1),log(spline(:,2)),'-b')
xlabel('time [months]')
ylabel('ln(field mice population)')
legend('linear interpolation','spline interpolation')
12
linear interpolation
spline interpolation
11
ln(field mice population)
10
9
8
7
6
5
4
0
1
2
3
4
5
6
7
8
time [months]
Figure 12.6 Comparison of linear and spline interpolation of b, where here
we plot the log of the population.
The functions still appear to be the same, so the use of spline made little difference
as compared to using linear interpolation.
12.6 Good results come from good users
Computers are great! They do exactly what they are told. But that is also the
biggest source of problems. If there is a typo in your code, MATLAB does not
notice this, it just keeps going. Finding such errors was part of our motivation for
incremental development.
On the same note, when interpolating, MATLAB does exactly what it is told. It
does not know if linear, pchip, or spline is the most appropriate for a model. It
just uses what you tell it to. You are irreplaceable. While in many cases it does not
make much of a difference whether you use linear or spline, sometimes the effect
on the accuracy can be very noticeable and very important.
Let’s consider two examples. First, let’s revisit the interpolation of b. We found
that the difference between linear and spline interpolation on the result of the
458
Chapter 12 Interpolation
solution to our ODE was small and not noticeable. Let’s take a closer look. Let’s
plot the reference data. Then, for a time range of 0 to 8 months in increments of
0.1 months, let’s perform linear interpolation and spline interpolation. Then plot
the results and compare to our reference data.
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
T = 0:8;
B = [70 36 11 1 4 13 28 43 56] * 1e-4;
plot(T,B,'ro')
hold on
Teval = 0:0.1:8;
linear = interp1(T, B, Teval,'linear',NaN);
spline = interp1(T, B, Teval, 'spline',NaN);
plot(Teval,linear,'-b')
plot(Teval,spline,'-g')
xlabel('time [months]')
ylabel('b')
legend('ref data','linear','spline')
7
×10 -3
ref data
linear
spline
6
5
b
4
3
2
1
0
0
1
2
3
4
5
6
7
8
time [months]
Figure 12.7 Comparison of linear and spline interpolation of b. Red circles
are reference data, blue line is linear interpolation, and green line is spline
interpolation.
We see that for the most part the results of linear and spline interpolation are in
good agreement with each other. The only noticeable difference is around the
minimum. Here the value of b is smallest and close to 0, and is why we probably
did not notice much of a difference.
Let’s consider another example for which we know the true, analytic answer,
which will allow us to directly compare the methods. Imagine you collected the
12.6 Good results come from good users
following data:
t
0
2
4
6
c (t )
1.0000
7.3891
54.5982
403.4288
This actually corresponds to c = exp(t ), which we will use to compare our results.
Let’s start by constructing a plot just as we did for b, where here we will also plot
the c = exp(t ) data as a reference.
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
T = 0:2:6;
C = [1.0000, 7.3891, 54.5982, 403.4288];
hold on
plot(T,C,'ro')
Teval = 0:0.1:6;
Cref = exp(Teval);
Linear = interp1(T, C, Teval,'linear',NaN);
Spline = interp1(T, C, Teval, 'spline',NaN);
Pchip = interp1(T, C, Teval,'pchip',NaN);
plot(Teval,Cref,'--r')
plot(Teval,Linear,'-b')
plot(Teval,Spline,'-g')
plot(Teval,Pchip,'-k')
xlabel('t')
ylabel('c')
legend('ref data','exp(t)','linear','spline','pchip')
459
460
Chapter 12 Interpolation
450
ref data
exp(t)
linear
spline
pchip
400
350
300
c
250
200
150
100
50
0
0
1
2
3
4
5
6
t
Figure 12.8 Comparison of linear, spline and pchip interpolation of c,
which is actually c = exp(t ). Red circles are reference data, red dashed
line is exp(t ) drawn for reference, blue line is linear interpolation, green line
is spline interpolation, and the black line is pchip interpolation.
We find that as time increases, the deviation between linear interpolation and the
reference curve increases. The agreement between the reference curve and spline
is better for t > 2. However, for t ≤ 2 , spline actually predicts a function that is
concave down. Here pchip does better, but like spline, it over predicts over the
range 4 > t > 6. We can compare numerical values at t = 5:
>> exp(5)
ans = 148.4132
>> interp1(T,C,5,'linear')
ans = 229.0135
>> interp1(T,C,5,'spline')
ans = 175.0107
>> interp1(T,C,5,'pchip')
ans = 176.9537
Even with spline this deviation corresponds to approximately 18%. This might be
okay for some applications, but not for others.
What are we to do? Have you ever heard of logarithmic graph paper before?
Natural log (ln) is log with base e. It received its name because ln and e appear
in many expressions when modeling physical systems. (Key point, they appear
12.6 Good results come from good users
461
a lot.) In the pre-MATLAB days there was, and still is today, a desire to plot data
such that it appears to be linear. Taking the log of an exponential function, or
a variable raised to a power, the result is a linear function. This is often called
linearization. Let us take the log of c, plot the result, and then see the result
of performing linear and spline interpolation on this new data set. I will then
un-linearize the interpolation results and plot them against the original reference
data.
LOGC = log(C);
plot(T,LOGC,'ro')
hold on
RefLOG = Teval;
LinearLOG = interp1(T, LOGC, Teval,'linear',NaN);
SplineLOG = interp1(T, LOGC, Teval, 'spline',NaN);
PchipLOG = interp1(T, LOGC, Teval, 'pchip',NaN);
plot(Teval,RefLOG,'--r')
plot(Teval,LinearLOG,'-b')
plot(Teval,SplineLOG,'-g')
plot(Teval,PchipLOG,'-k')
xlabel('t')
ylabel('ln c')
legend('ref data','x','linear','spline','pchip')
7
ref data
x
linear
spline
pchip
6
5
4
ln c
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
3
2
1
0
0
1
2
3
4
5
6
t
Figure 12.9 Comparison of linear, spline and pchip interpolation of ln(c),
where c is actually exp(t ), so ln(c) = t . Red circles are reference data, red
dashed line is t drawn for reference, blue line is linear interpolation, green
line is spline interpolation, and black line is pchip interpolation.
t Remember in MATLAB log
is ln, and log10 is log.
462
Chapter 12 Interpolation
Beautiful! All three methods result in a perfect match. Let’s write an anonymous
function to perform linear interpolation of ln c versus t , and then to return the
value of c, which is what we actually want. We can then calculate the value at
t = 5 to compare to our previous result. In writing the function, I will assume we
just have our original data tabulated as T and C.
>> ct = @(t) exp( interp1(T,log(C),t,'linear',NaN) );
>> ct(5)
ans = 148.4132
In perfect agreement with the reference curve. This is a huge improvement over
performing interpolation of c versus t , and it required minimal extra work. And
that is what I mean by “good results come from good users.”
Example 12.3 Interpolating vapor pressure data.
Below I have tabulated vapor pressure data for benzene which was taken from
the NIST Chemistry WebBook, although you can pretend you collected it in the
laboratory.
T [K]
300
320
340
360
380
400
420
440
460
480
500
P sat [bar]
0.1381
0.32029
0.66128
1.2425
2.1614
3.5284
5.4649
8.1007
11.575
16.037
21.651
where the temperature is in K and the pressure is in bar. Plot the data, and compare
the use of linear and spline interpolation. (Follow the work in Section 12.6.) How
do the two methods perform? Use them both to estimate the vapor pressure at 390
K.
From your thermodynamics class, you know that the Clausius-Clapeyron equation
suggests that ln p sat scales linearly with T −1 . Transform the data as suggested by
the Clausius-Clapeyron equation and re-plot it, and again compare the use of
linear and spline interpolation. Use them both to estimate the vapor pressure at
390 K.
How do the results compare?
12.6 Good results come from good users
463
Solution:
Let’s begin by entering the data in to two vectors, T_K and Psat_bar. The temperature data is evenly spaced, so I will use the extended colon operator.
t Psat_bar is long, so I will
>> T_K = 300:20:500;
>> Psat_bar = [0.1381,0.32029,0.66128,1.2425,2.1614,3.5284,5.4649,...
8.1007,11.575,16.037,21.651];
Next, to graphically compare the use of linear and spline interpolation, let’s use
linear and spline interpolation to estimate the vapor pressure over the range 300
to 500 K in increments of 1 K. To do this, we will create a new vector T_eval which
contains the temperatures we wish to estimate the vapor pressure at. We can then
use interp1 with either linear or spline interpolation, and store the results to two
new vectors. We will then plot the original data and our interpolation results, and
compare the two. Remember, with linear and spline interpolation, we are only
estimating the data in between points.
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
T_eval = 300:1:500;
Psat_linear = interp1(T_K,Psat_bar,T_eval,'linear',NaN);
Psat_spline = interp1(T_K,Psat_bar,T_eval,'spline',NaN);
plot(T_K,Psat_bar,'kx')
hold on
plot(T_eval,Psat_linear,'-r')
plot(T_eval,Psat_spline,'--b')
xlabel('T [K]')
ylabel('Psat [bar]')
legend('reference','linear','spline')
break it up onto two lines.
To continue onto another
line, we need to add ... to
the end of the line. Then to
get to the next line in the
Command Window, use
Shift + Enter. Know that
if I were to omit the ...,
MATLAB would interpret the
second line as the second
column in matrix.
464
Chapter 12 Interpolation
25
reference
linear
spline
Psat [bar]
20
15
10
5
0
300
350
400
450
500
T [K]
Figure 12.10 Comparing our original reference data to the result using
linear and spline interpolation.
As far as we can tell from the figure, linear and spline interpolation appear to be
in close agreement, and appear to represent our data well. Next, let’s use both
methods to estimate the vapor pressure at 390 K.
>> Psat_390_linear = interp1(T_K,Psat_bar,390,'linear',NaN)
Psat_390_linear =
2.8449
>> Psat_390_spline = interp1(T_K,Psat_bar,390,'spline',NaN)
Psat_390_spline =
2.7815
Quantitatively we do find a small difference in the two methods.
Next we are told to repeat, but plot our data as a Clapeyron plot and to interpolate
in this fashion. Then we will again make predictions at 390 K. For this case we will
interpolate using our linearized data, ln p sat versus 1/T , and then un-linearize to
get p sat .
>>
>>
>>
>>
>>
>>
close % Close our open figure and start fresh
Tinv = 1./T_K;
Ln_Psat = log(Psat_bar);
Teval_inv = 1./T_eval;
Ln_Psat_linear = interp1(Tinv,Ln_Psat,Teval_inv,'linear',NaN);
Ln_Psat_spline = interp1(Tinv,Ln_Psat,Teval_inv,'spline',NaN);
12.6 Good results come from good users
>>
>>
>>
>>
>>
>>
>>
465
plot(Tinv,Ln_Psat,'kx')
hold on
plot(Teval_inv,Ln_Psat_linear,'-r')
plot(Teval_inv,Ln_Psat_spline,'--b')
xlabel('1/T [1/K]')
ylabel('ln Psat/bar')
legend('reference','linear','spline')
4
reference
linear
spline
3
ln Psat/bar
2
1
0
-1
-2
2
2.2
2.4
2.6
2.8
3
3.2
1/T [1/K]
3.4
×10 -3
Figure 12.11 Comparing our original reference data to the result using
linear and spline interpolation. Here we use ln p sat versus 1/T to linearize
our data.
Again, as far as we can tell from the figure, linear and spline interpolation appear
to be in close agreement, and appear to represent our data well. As compared to
before, our transformed data now appears to be linear. While linear and spline
were in close agreement before, I suspect it is just because of the spacing used; over
the small range between data points, linear interpolation was a fair approximation.
Here, we expect linear to work well because the data appears to be linear. Let’s
see by using both linear and spline interpolation to estimate the vapor pressure at
390 K. We will interpolate using our linearized data, then un-linearize to get the
desired result.
>> ln_Psat_390_linear = interp1(Tinv,Ln_Psat,1/390,'linear',NaN)
ln_Psat_390_linear =
1.0221
>> Psat_390_linear_clap = exp(ln_Psat_390_linear)
466
Chapter 12 Interpolation
Psat_390_linear_clap =
2.7790
>> ln_Psat_390_spline = interp1(Tinv,Ln_Psat,1/390,'spline',NaN)
ln_Psat_390_spline =
1.0230
>> Psat_390_spline_clap = exp(ln_Psat_390_spline)
Psat_390_spline_clap =
2.7815
Using spline interpolation, we find that we obtain the same result whether we
interpolate using the original data or the linearized data. Using linear interpolation,
using our linearized date the result in now in closer agreement with the result using
spline interpolation.
12.7 Fun with tabulated pure component VLE data (i.e.,
the saturated steam tables)
Most thermodynamics 1 courses rely heavily on reading tabulated data of pure
fluids. Most typically for Water (the steam tables) and Refrigerant 134a. However,
the exact conditions you often need are in between points, requiring interpolation.
What might be better is the use of an analytic equation of state, possibly fit to
the tabulated data, obviating the need for interpolation. But this is not without
challenge, especially for two-phase equilibria and non-pV T (pressure, volume,
temperature) data.
In the remainder of the chapter we will demonstrate the use of MATLAB as
the ultimate equation of state. We will read in the saturated steam tables and
use interp1 to effortlessly interpolate and extrapolate. Essentially, MATLAB will
provide us with an analytic form of the steam tables.
12.7.1 Downloading the saturated steam tables
t If you are having difficulties downloading a data set
from NIST, a copy of my
file for water can be downloaded here.
We will download the saturated steam tables from the NIST WebBook Thermophysical Properties of Fluid Systems.3 While here we will look at water, other
fluids are available, and the database keeps growing.
For the species, from the drop-down menu select “Water,” and then choose
your preferred units. Comparing to typically steam tables in the appendix of a
thermodynamics text for temperature, pressure, density, and energy, I will choose
K, MPa, g/mL and kJ/kg respectively. And since we will look at the saturated steam
tables, I choose “Saturation properties – temperature increments.” You could
3 http://webbook.nist.gov/chemistry/fluid/
12.7 Fun with tabulated pure component VLE data (i.e., the saturated steam tables)
467
alternatively look at “pressure increments,” but temperature increments seem
more common. The other options would be used for supercritical, superheated
vapor, and compressed liquid phases.
Figure 12.12 NIST WebBook Thermophysical Properties of Fluid Systems
units settings
On the next window we select the temperature range and the temperature
increments. The minimum value listed corresponds to the triple point, and the
maximum value corresponds to the critical point. The two-phase vapor-liquid
region is between these two points. I will choose 280 to 640 K in increments of 10
K.
Figure 12.13 NIST WebBook Thermophysical Properties of Fluid Systems
temperature settings
While lots of information is available on the next page, we would like to download the tabulated data. This can be accomplished by right-clicking on “Download
data as tab-deliminated text file.” On my computer the default name comes up as
“fluid.cgi.” I will save to my current working directory as “water_sat_nist.dat.” The
other useful piece of information to note before closing our web browser is the
“Auxiliary data.” Of particular note is the “Reference States.” Remember, there is
no such thing as absolute energy. We only know energy within a constant. And
t I mention again that if you
are having difficulties downloading a data set from
NIST, a copy of my file for
water can be downloaded
here.
468
Chapter 12 Interpolation
that is okay. In thermodynamics, we are only interested in differences. We use
reference states to simplify data tabulation.
Figure 12.14 The reference states used by the NIST WebBook Thermophysical Properties of Fluid for water, along with the critical temperature (Tc ),
critical pressure (p c ), and critical density (ρ c ), accentric factor (ω), normal
boiling point (Tb ), and the dipole moment.
12.7.2 dlmread
t As a starting point to learn
more about importing data
into MATLAB, have a look
at the documentation page
“Ways to Import Text Files”.
Now that we have tabulated data, we need to read it into MATLAB. There are
several ways to read-in data in MATLAB. Here we will use dlmread which is well
suited for this case. The “dlm” stands for a “deliminated” text file. A deliminated
text file is a file that contains columns of data separated by a fixed character
(typically a comma) or space (or tab). We will first go over the syntax of dlmread,
one required argument and three optional arguments, and then we will look at
the specific example of the saturated steam tables. If you would like more, have a
look at the MATLAB documentation. Know that a limitation of dlmread is that it
can only read in numeric data. There are ways to read mixed data types, but they
are more involved and will be reserved for later.
The base use would be M = dlmread(filename). filename is the name of
the file you wish to read-in and is a string. For our case this will be ‘water_sat_nist.dat’.
Note that the quotes are important to indicate that it is a string. dlmread reads in
the contents of the file and stores it in matrix M. Remember a deliminated text file
is a file that contains columns of data. Each column of the text file will be stored
to a column in M, in the order that they appear.
The first optional argument is delimiter, resulting in M = dlmread(filename,delimiter).
delimiter allows you to specify what is separating the columns in the text file.
In the previous case, when we did not specify delimiter, MATLAB detects the
12.7 Fun with tabulated pure component VLE data (i.e., the saturated steam tables)
469
delimiter from the file and treats repeated white spaces as a single delimiter.
delimiter is also a string. To specify the delimiter as a space, comma, and tab,
use ’ ’, ’,’, and ’\t’, respectively. Again, the quotes are important to indicate
that it is a string. When we downloaded the saturated steam tables, the website
indicated that it was a tab-deliminated text file, so we will use ’\t’. Note that if
you would like to stick with the default of MATLAB determining the delimiter, you
can use quotes with no space in between, “; this is also referred to as an empty
character.
The second optional argument is the the row, R1, and column, C1, offset,
resulting in M = dlmread(filename,delimiter,R1,C1). If you would like to
read-in all of the data, then R1=0 and C1=0. Recall that dlmread can only be used
to read in numeric data. In our case, the first row will be a string indicating the
property in each column. We will therefore need to skip (not read-in) the first row.
This is accomplished with R1=1 and C1=0. This means use a row offset of 1, i.e.
skip the first row, but don’t skip any columns.
The fourth optional argument is to specify the row and column range as
[R1 C1 R2 C2], resulting in M = dlmread(filename,delimiter,[R1 C1 R2
C2]). Here R1, R2, C1, and C2 are still row and column offsets. So if we wanted
to read in rows 1 to 6 of columns 1 and 2, we would have R1=0, R2=5, and C1=0,
C2=1. So if you would like to think in terms of row and column numbers rather
than row and column offsets, you need just recognize they differ by 1.
So finally, let’s read in the steam tables:
t If your text file was in a
>> M=dlmread('water_sat_nist.dat','\t',1,0);
I suppressed the output because the saturated steam tables contain a large
amount of data. If we would like to check the size of M, we can:
>> whos M
Name
M
Size
37x25
Bytes Class
7400 double
Attributes
So M is a matrix with 37 rows and 25 columns. What does each row correspond to?
You could find out by viewing “water_sat_nist.dat” in your favorite text editor. To
view the file within the MATLAB Command Window, you can use the command
type ’water_sat_nist.dat’. I do not do so here in the text because the file
is very large. You might find that the columns do not appear to be exactly lined
up. That is okay. In each row the data are separated by a tab, indicating the next
column. Where the columns do not appear to line up is due to differences in the
number of digits in a number or the use of scientific notation. You will find that
the columns correspond to the following:
different directory, you
could still load it, you would
just need to augment its
name to provide its location. If the file were in my
home directory, located
at “/home/paluchas”, we
could replace the file name
with “/home/paluchas/water_sat_nist.dat”. You can
always use global commands like this. Local commands are also possible if
you know where the file is
in relation to the current
directory.
470
Chapter 12 Interpolation
1. Temperature (K)
2. Pressure (bar)
3. Density (l, mol/l). Here the first “l” is used to indicate the phase as liquid.
The second “l” corresponds to liters, which should actually be capitalized,
“L”.
4. Volume (l, l/mol)
5. Internal Energy (l, kJ/mol)
6. Enthalpy (l, kJ/mol)
7. Entropy (l, J/mol*K)
8. Cv (l, J/mol*K)
9. Cp (l, J/mol*K)
10. Sound Spd. (l, m/s)
11. Joule-Thomson (l, K/bar)
12. Viscosity (l, uPa*s)
13. Therm. Cond. (l, W/m*K)
14. Surf. Tension (l, N/m)
15. Density (v, mol/l). Now here “v” stands for vapor phase.
16. Volume (v, l/mol)
17. Internal Energy (v, kJ/kg)
18. Enthalpy (v, kJ/kg)
19. Entropy (v, J/g*K)
20. Cv (v, J/g*K)
21. Cp (v, J/g*K)
22. Sound Spd. (v, m/s)
23. Joule-Thomson (v, K/MPa)
24. Viscosity (v, Pa*s)
25. Therm. Cond. (v, W/m*K)
12.7 Fun with tabulated pure component VLE data (i.e., the saturated steam tables)
That is a lot of data! If I look at typical saturated steam tables in the appendix of a
thermodynamics text, they typically contain just temperature, pressure, density,
internal energy, enthalpy, and entropy. Let us therefore create a new matrix,
water_sat that contains just this data as columns:
1. Temperature (K)
2. Pressure (bar)
3. Density (l, mol/l)
4. Internal Energy (l, kJ/mol)
5. Enthalpy (l, kJ/mol)
6. Entropy (l, J/mol*K)
7. Density (v, mol/l)
8. Internal Energy (v, kJ/kg)
9. Enthalpy (v, kJ/kg)
10. Entropy (v, J/g*K)
To accomplish this, we see we will need all rows and columns 1–3, 5–7, 15, and
17–19. This is accomplished as:
>> water_sat = [M(:,1:3),M(:,5:7),M(:,15),M(:,17:19)];
You may find that you would like to look at many of the systems provided in the
NIST WebBook Thermophysical Properties of Fluid Systems database, and this
might seem like a lot to do each time (and to remember!). We can therefore create
a function to generalize and automate the process:
471
472
Chapter 12 Interpolation
Listing 12.6 import_sat_vle.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
% function res = import_sat_vle(sat_vle_nist)
%
% Imporating tabulated saturated VLE data from NIST
%
% res will be the matrix of desired properties (type numeric).
%
By default this will by T, P, rho L, U L, H L, S L,
%
rho V, U V, H V, S V
%
but you can easily modify this.
%
% sat_vle_nist will be a string corresponding to the file name.
%
be sure to include the path if it is not in the current
%
directory.
%
function res = import_sat_vle(sat_vle_nist)
% Read in our saturated VLE data from NIST, skipping
% the first row which corresponds to the column labels
M=dlmread(sat_vle_nist,'\t',1,0);
% Columns are labeled as:
% 1) Temperature (K)
% 2) Pressure (MPa)
% 3) Density (l, mol/l)
% 4) Volume (l, l/mol)
% 5) Internal Energy (l, kJ/mol)
% 6) Enthalpy (l, kJ/mol)
% 7) Entropy (l, J/mol*K)
% 8) Cv (l, J/mol*K)
% 9) Cp (l, J/mol*K)
%10) Sound Spd. (l, m/s)
%11) Joule-Thomson (l, K/bar)
%12) Viscosity (l, uPa*s)
%13) Therm. Cond. (l, W/m*K)
%14) Surf. Tension (l, N/m)
%15) Density (v, mol/l)
%16) Volume (v, l/mol)
%17) Internal Energy (v, kJ/kg)
%18) Enthalpy (v, kJ/kg)
%19) Entropy (v, J/g*K)
%20) Cv (v, J/g*K)
%21) Cp (v, J/g*K)
%22) Sound Spd. (v, m/s)
%23) Joule-Thomson (v, K/MPa)
%24) Viscosity (v, Pa*s)
%25) Therm. Cond. (v, W/m*K)
end
% Creating a matrix with specified columns.
% By default this will by T, P, rho L, U L, H L, S L,
%
rho V, U V, H V, S V
% but you can easily modify this. This will be returned.
res = [M(:,1:3),M(:,5:7),M(:,15),M(:,17:19)];
%res = [M(:,1),M(:,14)];
12.7 Fun with tabulated pure component VLE data (i.e., the saturated steam tables)
Let’s give it a try:
>> water_sat = import_sat_vle('water_sat_nist.dat');
Nice!
12.7.3 interp1
Now let’s interpolate! Here we are dealing with a pure component system (N = 1)
at vapor-liquid equilibrium (π = 2). According to Gibbs phase rule, we have
F = N −π+2 = 1−2+2 = 1, a single degree of freedom. That is, we need only specify a single, intensive, thermodynamics function to pin down the thermodynamic
state of our system. What does that mean? You can think of it as every thermodynamic property is a function of only a single variable. This will correspond to
interpolating in 1-D using interp1.
Remember, our data is tabulated from 280 to 640 K in increments of 10 K.
What if we needed the properties of saturated water at 298.15 K? Are we out of
luck? No, we can use interp1. Remember temperature is column 1 of water_sat.
Using spline interpolation:
>> sol = interp1(water_sat(:,1),water_sat,298.15,'spline')
sol =
1.0e+03 *
Columns 1 through 6
0.2981
0.0000
0.0010
0.1048
2.5465
0.0086
0.1048
0.0004
Columns 7 through 10
0.0000
2.4091
We see that we are able to simultaneously interpolate on every column of water_sat.
The result, sol, is a row vector with each column corresponding to the function
in the same column of water_sat. The one issue you will notice is the number
of decimal places used when displaying the result. (MATLAB actually uses more
decimal places in its calculations, it just displays fewer.) Right now we have a
pressure of zero and density of the vapor of zero, which certainly can not be the
case. The issue is MATLAB is multiplying everything by a factor of 1,000; it is
trying to use the best overall scale, which might not be the best for each individual case. There are number of workarounds. The easiest is just to display the
specific elements in question. For the case of pressure, the second element of sol:
473
474
Chapter 12 Interpolation
>> p_MPa = sol(2)
p_MPa =
0.0032
We could go further and use different units if MPa is not appropriate at this
temperature. Instead of MPa, we could use kPa by multiplying by a factor of 1,000:
>> p_kPa = sol(2)*1000
p_kPa =
3.1695
Or you could just interpolate in pressure if that is what you are interested
in. What is important to remember is that while MATLAB may display zero, the
pressure and vapor density are not actually zero. It is just a limit of the number of
decimal places displayed. Or you could just tell MATLAB to display more decimal
places:
>> format long
>> sol = interp1(water_sat(:,1),water_sat,298.15,'spline')
sol =
1.0e+03 *
Columns 1 through 3
0.298150000000000
0.000003169460821
0.000997001514381
0.104824285101334
0.000367225663839
2.409079489426976
2.546546392886929
Columns 4 through 6
0.104824990686921
Columns 7 through 9
0.000000023072679
Column 10
0.008556666952043
Hmmmm, scientific notation might be better:
>> format short e
>> sol = interp1(water_sat(:,1),water_sat,298.15,'spline')
12.7 Fun with tabulated pure component VLE data (i.e., the saturated steam tables)
sol =
Columns 1 through 4
2.9815e+02
3.1695e-03
9.9700e-01
1.0482e+02
2.3073e-05
2.4091e+03
Columns 5 through 8
1.0482e+02
3.6723e-01
Columns 9 through 10
2.5465e+03
8.5567e+00
Besides temperature, you can interpolate in any variable you wish. For example, what are the properties of our two phase system at a pressure of 0.3 MPa?
Pressure is the second column of water_sat:
>> sol = interp1(water_sat(:,2),water_sat,0.3,'spline')
sol =
Columns 1 through 4
4.0668e+02
3.0000e-01
9.3182e-01
5.6112e+02
1.6509e-03
2.5431e+03
Columns 5 through 8
5.6143e+02
1.6718e+00
Columns 9 through 10
2.7249e+03
6.9915e+00
Or how about when the liquid density is 0.8 g/mL? The liquid density is the third
column of water_sat:
475
476
Chapter 12 Interpolation
>> sol = interp1(water_sat(:,3),water_sat,0.8,'spline')
sol =
Columns 1 through 4
5.2240e+02
3.9262e+00
8.0000e-01
1.0772e+03
1.9709e-02
2.6020e+03
Columns 5 through 8
1.0821e+03
2.7867e+00
Columns 9 through 10
2.8011e+03
6.0773e+00
And the list goes on and on.
12.7.4 Plotting phase diagrams
It is great that MATLAB can interpolate the steam tables for us. But it can do more.
A benefit of using MATLAB over conventional steam tables is that we can readily
generate phase diagrams, which in turn can help us better understand our system
and phase-equilibria thermodynamics in general. Many great contributions in
the field have come from understanding and generalizing trends.
Let’s start with the T ρ plane. That is, let’s plot T versus ρ L and ρV , the density
of the liquid and vapor phase. This is a fun case because the density of the two
phases are different, resulting in a “phase envelope”. If you are at a point under
the dome, your system will split along the isotherm to form a vapor and liquid in
coexistence with each other.
>>
>>
>>
>>
>>
hold on
plot(water_sat(:,3),water_sat(:,1),'-ro')
plot(water_sat(:,7),water_sat(:,1),'-bo')
xlabel('Density [g/mL]')
ylabel('Temperature [K]')
12.7 Fun with tabulated pure component VLE data (i.e., the saturated steam tables)
650
600
Temperature [K]
550
500
450
400
350
300
250
0
0.2
0.4
0.6
0.8
1
Density [g/mL]
Figure 12.15 A plot of the T ρ plane of water at vapor-liquid equilibrium.
We know that the liquid density is larger than the vapor density, so is given by the
red line to the right of the diagram. As temperature increases, the liquid density
decreases while the vapor density increases. At the critical point, the two phases
become one, so the densities approach the same value, the critical density, at
the critical point. To the left of the dome is the vapor region, to the right is the
compressed liquid region, and above would be the supercritical region. Our dome
does not close up on top because our maximum temperature is 640 K while the
critical temperature is 647.096 K. We can extrapolate to find this value, but it is a
difficult task; as you might be able to tell from the diagram, the phase envelope is
flattening out near the critical point.
However, we just said that the density of the two phases should be equal at
the critical point, or that the difference in the density of the two phases should go
to 0. Let’s create a new vector which is the difference in the two densities and add
it to our plot. I will subtract the vapor density from the liquid density so that the
result is a positive number. Then I will extrapolate to find the temperature where
this function is equal to zero, which should correspond to the critical point. I will
then add the critical point to the plot.
477
478
Chapter 12 Interpolation
>> rho_diff = water_sat(:,3) - water_sat(:,7);
>> plot(rho_diff,water_sat(:,1),'-k')
>> sol = interp1(rho_diff,water_sat,0,'spline','extrap')
sol =
Columns 1 through 4
6.5309e+02
2.0483e+01
3.1966e-01
2.0275e+03
3.1966e-01
2.0185e+03
Columns 5 through 8
2.0953e+03
4.4283e+00
Columns 9 through 10
2.0949e+03
4.3552e+00
>> plot(sol(3),sol(1),'kx')
700
650
Temperature [K]
600
550
500
450
400
350
300
250
0
0.2
0.4
0.6
0.8
1
Density [g/mL]
Figure 12.16 A plot of the T ρ plane of water at vapor-liquid equilibrium
with an estimate of the critical point.
We estimate a critical temperature of 653.09 K, a critical pressure of 20.483 MPa,
and a critical density of 0.31966 g/mL. As compared to the reference NIST data,
we overestimate the temperature by about 6 K, underestimate the pressure by
about 2 MPa, and underestimate the critical density by approximately 0.002 g/mL.
That’s not too bad for a prediction.
Let’s next plot the hT plane, where h is the molar enthalpy. That is, let’s plot
12.7 Fun with tabulated pure component VLE data (i.e., the saturated steam tables)
h L versus T and hV versus T . We get a phase envelope very similar to the ρT case.
However, here on the right we have vapor and on the left we have liquid. (The
internal energy of a vapor will be greater than a liquid.)
>>
>>
>>
>>
>>
hold on
plot(water_sat(:,5),water_sat(:,1),'-ro')
plot(water_sat(:,9),water_sat(:,1),'-bo')
xlabel('Enthalpy [kJ/kg]')
ylabel('Temperature [K]')
650
600
Temperature [K]
550
500
450
400
350
300
250
0
500
1000
1500
2000
2500
3000
Enthalpy [kJ/kg]
Figure 12.17 A plot of the T h plane of water at vapor-liquid equilibrium.
At the critical point, where the two phases go to one, the enthalpies should
approach the same value. Let us calculate the enthalpy of vaporization, ∆h vap =
hV − h L , and plot it on our graph.
>> h_vap = water_sat(:,9) - water_sat(:,5);
>> plot(h_vap,water_sat(:,1),'-k')
479
480
Chapter 12 Interpolation
650
600
Temperature [K]
550
500
450
400
350
300
250
0
500
1000
1500
2000
2500
3000
Enthalpy [kJ/kg]
Figure 12.18 A plot of the T h plane of water at vapor-liquid equilibrium.
The black curve corresponds to the enthalpy of vaporization.
We find that as the temperature increases, the enthalpy of vaporization decreases,
until it ultimately goes to zero at the critical point. The other point to note is that
the enthalpy of vaporization is not constant, which is assumed when using the
Clausius-Clapeyron equation to extrapolate vapor pressure data... so only use the
Clausius-Clapeyron equation over narrow temperature ranges.
We can go on and on, but I think you get the picture.
12.8 Glossary
interpolation: Estimating the value of a function using known values on either
side.
extrapolation: Estimating the value of a function using known values that don’t
bracket the desired value.
single-valued mapping: A mapping where each value in the range maps to a
single value in the domain.
multivalued mapping: A mapping where at least one value in the range maps to
more than one value in the domain.
12.9 Exercises
481
12.9 Exercises
Exercise 12.1 Thermistors are used to measure the temperature of bodies. Thermistors
are based on materials’ change in resistance with temperature. To measure temperature,
manufacturers provide you with a temperature versus resistance calibration curve. If
you measure resistance, you can find the temperature. A manufacturer of thermistors
makes several observations with a thermistor, which are given at the end of the problem.
Determine the temperature corresponding to 754.8 ohms.
R (ohm)
1101.0
911.3
636.0
451.1
T (◦ C)
25.113
30.131
40.120
50.128
Exercise 12.2 To try and estimate the critical point of water, we extrapolated to estimate
where the difference between the liquid and vapor density were zero. We could have
done the same thing with the enthalpy of vaporization, but given the shape of the phaseenvelope, we would expect similar results. Another approach pioneered by Guggenheim
(a really, really smart thermodynamicist) was to plot the surface tension versus the
temperature. The surface tension is the interfacial (vapor/liquid) free energy per unit
area. At the critical point the free energy barrier between the phases goes to zero, so the
surface tension goes to zero.
Try Guggenheim’s method. Plot surface tension versus temperature, then estimate
the critical point by extrapolating to where the surface tension is zero. Based on the plot,
do you expect the result to be more promising? To help you import the necessary data,
you can use the updated function import_sat_vle_gug.
As an aside, Guggenheim’s method is really a very powerful tool, that is often unappreciated. It’s greatest utility is estimating the critical point for non-volatile fluids; I have
seen the critical point of NaCl estimated in this way. Now why would someone want an
estimate of the critical point of NaCl? So that we can model its phase behavior using an
equation of state and to use or develop corresponding states theories.
13
Chapter
Numerical Integration
In Chapter 13 we learn how to numerically compute the definite integral of a
function with a single variable. By the end of this chapter you will be able to:
• Provide a basic explanation of how to numerically compute the definite
integral of a function with a single variable
• Exhibit ability to construct a function for the expression to be integrated
• Apply integral to compute definite integrals
• Use integral to compute definite integrals with tabulated data.
If you work through the chapter and believe these goals are not met, please
re-review the material and reach out for help.
Some of the material on numerical integration is adopted from Rachel Duke
and Spencer Sabatino from this course during Spring 2020.
13.1 Integration
Integration is a fundamental operation of Calculus and is important for the solution of a wide range of engineering problems. Considering a function with a
single variable, the definite integral of the function corresponds to the signed area
of the function in the plane between two points (i.e., the “area under the curve”).
For some function, the definite integral may readily be computed analytically. For
example:
·
¸¯
Z 2
1 3 ¯¯2 1 3 1 3
2
x dx =
x ¯ = 2 − 0 = 8/3 = 2.6667
3
3
3
0
0
The “Power Rule” is one I remember. Within this expression, recall x 3 /3 is the
antiderivative of x 2 . Others functions may be more complicated. However, many
483
484
Chapter 13 Numerical Integration
antiderivates may be found in a list of integrals. But some may elude us. Consider
the following:
Z 2
2
e −x d x
0
which can not be evaluated analytically nor expressed in elementary functions.
This function can, however, be evaluated numerically.
Numerical integration is the practice of applying various algorithms and approximations to solve definite integrals. We often desire to employ numerical
techniques for integration for a few reasons. First, as shown in expression section 13.1, our integral expression might be impossible to evaluate analytically, or
at least without elementary functions, preventing us from solving by hand. Another big reason is simply because it would be quicker for us to evaluate integrals
indirectly using these methods without a need to consult a list of integrals. This is
similar to the motivation for solving ordinary differential equations numerically
with ode45. For example, while section 13.1 could readily be solved analytically,
to give you a preview of what’s to come, the numerical solution with MATLAB is
as easy as:
>> fune = @(X) X.^(2);
>> sol = integral(fune,0,2)
sol = 2.6667
And even the impossible becomes possible!
>> fune = @(X) exp(-X.^(2));
>> sol = integral(fune,0,2)
sol = 0.8821
Essentially, all that you need is a function for your function of interest.
Recall the formal definition of an integral:
Z
b
a
f (x) = lim
n→∞
n
X
¡ ¢
f x i∗ ∆x
i =1
While this definition may look involved, all it ultimately is saying is that a definite
integral is an infinite summation of the area of infinitesimally small rectangles of
width ∆x and height f (x ∗ ). This definition ultimately forms the basis of several
common numerical integration techniques as we’ll discuss next.
13.2 Numerical Integration
As one would anticipate, there are numerous ways to numerically evaluate integrals. Our first idea originates with the definition from section 13.1. Rather than
use an infinite amount of infinitely small rectangles, we can elect to approximate
the area under the curve with a finite amount of uniform rectangles. This technique is commonly introduced in your Calculus 1 courses as Riemann Sums, and
13.2 Numerical Integration
485
these approximations often work well. The orientation of the rectangle is often
important too, as you could orient the rectangles so that the left side, right side, or
midpoint of the rectangle is on the curve, often denoted as left hand, right hand,
or midpoint sums.
8
7
6
5
4
3
2
1
0
0
0.5
1
1.5
Figure 13.1 The left-hand sum of x 3 over the range 0 to 2. Image from
Wikipedia.
2
486
Chapter 13 Numerical Integration
8
7
6
5
4
3
2
1
0
0
0.5
1
1.5
2
Figure 13.2 The right-hand sum of x 3 over the range 0 to 2. Image from
Wikipedia.
8
7
6
5
4
3
2
1
0
0
0.5
1
1.5
Figure 13.3 The midpoint sum of x 3 over the range 0 to 2. Image from
Wikipedia.
2
13.2 Numerical Integration
487
We see that the computed integral wil be sensitive to how the rectangles are constructures. Additionally, as ∆x decrases we know that the accuracy will increase.
That’s not the end of it though; why should we be limited to rectangles?
Why not try more curved and unique shapes to better match the curve we’re
working with? That’s where we run into the trapezoidal method, which relies
upon summing the area of finitely many trapezoids instead of rectangles. Similar
to our discussion of Euler’s method, let us assume that the function is linear over
the range ∆x. The integral over this range is equivalent to the area of a trapezoid,
1
2 b (h 1 + h 2 ). Here, b = x 0 + ∆x − x 0 = ∆x, h 1 = f (x 0 ) and h 2 = f (x 0 + ∆x). The
smaller the value of ∆x, the better the linear approximation, and in the limit that
∆x → 0 we expect the relation to be exact.
8
7
6
5
4
3
2
1
0
0
0.5
1
1.5
2
Figure 13.4 Application of the trapezoid method for x 3 over the range 0 to 2.
Image from Wikipedia.
Let’s us view the trapezoid method from a different angle. Namely, earlier I
mentioned that power rule was one that I actually remembered. That is, I could
readily compute the integral of a polynomical analytically. The trapezoid method
assumes that the funciton is linear over the range x 0 to x 0 + ∆x. We can readily
determine the equation of the line over this range as g (x) = mx + b where:
m=
f (x 0 + ∆x) − f (x 0 )
∆x
and
b = f (x 0 ) − x 0
f (x 0 + ∆x) − f (x 0 )
∆x
488
Chapter 13 Numerical Integration
where here g (x) corresponds to the linear equation used to approximate the
function f (x). Now integrating over the range x 0 to x 0 + ∆x:
Z x0 +∆x
hm
i¯
¡
¢
1
¯
x 2 + bx ¯ 0x0 +∆x = ∆x f (x 0 + ∆x) − f (x 0 )
(mx + b) d x =
2
2
x
x0
Why limit ourselves to assuming the function is linear? We could readily fit
any order polynomial which could be integrated analytically. We would expect
that higher order polynomials could better represent the function of interest over
a range ∆x, allowing for a more accurate estimate of the integral.
Additionally, I point out that ∆x need not be constant when numerically integrating. There may be some regions where smaller or larger values of ∆x are
needed to achieve a given level of accuracy. A similar observation was made
with Euler’s method. Fortunately for us, MATLAB provides an optimized function to perform numerical integration, integral which can determine the most
appropriate method and optimal ∆x.
13.2.1 integral
Evaluating definite integrals in MATLAB is rather straightforward. And if you have
made it this far in the course, I hope you find it easy, especially compared to your
Calculus course. Essentially, all that is needed is a function for the function of
interest (with a single independent variable) that you wish to integrate. Just as
with fzero, fsolve, and ode45, the inputs to this function are limited. Namely
here the function can only have a single input, the independent variable. Parameters will need to be passed via the use of anonymous functions or nested
functions. We then only need to specify the range of interest. Let’s give it a try and
evaluate the integral of a function whose antiderivative cannot be expressed in
closed form:
Z 10
p −x
xe d x
0
>> fun = @(X) sqrt(X).*exp(-X);
>> sol = integral(fun,0,10)
sol = 0.8861
We see our first step was to create a function. This could be in a separate function
file, or here I use an anonymous function. When using integral, your function
needs to be vectorized. Next, we use integral. In its basic form, the first variable
is the function handle for the function to integrate, the second variable is the
lower-limit, and the third argument is the upper-limit. Remember, an anonymous
function is alread of type function handle, so @ is not needed. If you have used a
separate function file, then we would need @. That’s it!
The integral function is fairly robust, and can even handle infinite limits.
Consider:
Z ∞
p −x
1p
xe d x =
π
2
0
13.2 Numerical Integration
489
>> sol = integral(fun,0,inf)
sol = 0.8862
>> sol_check = 0.5*sqrt(pi)
sol_check = 0.8862
Nice! Next, let’s have some fun! Let’s create an anonymous function to evaluate
the integral for an arbitrary upper-limit.
>> int_fun = @(xlim) integral(fun,0,xlim);
>> sol = int_fun(10)
sol = 0.8861
This is a function with a single input and single output. We can use fplot
to plot the intgral as a function of the upper-limit. Note that here we can use a
lower-limit of 0 since the integral from 0 to 0 is just... 0. This will not result in an
error as we had with ode45.
>> fplot(int_fun,[0,10])
0.8
0.7
0.6
0.5
0.4
0.3
0.2
0.1
0
0
2
4
6
8
10
Figure 13.5 fplot(int_fun,[1e-6,10])
Nice! Wait, we can do more. What is you wanted to know the value of the
upper-limit for which the integral was equal to 0.5? Well, again, we have a function
with a single input and a single output. That sounds like something I can use with
our old friend fzero. We can set-up our error function in a number of ways. Here
I will use what I suspect is the most clear.
490
Chapter 13 Numerical Integration
>> int_fun_error = @(xlim) integral(fun,0,xlim) - 0.5;
>> sol = fzero(int_fun_error,[0,10])
sol = 1.3630
Awesome!
13.2.2 Working with Tabulated Data
We have seen that the first variable required to use integral is of type function
handle. Therefore, integral can not directly be used to integrate tabulated data.
However, it is possible if we combine with our friend interp1. Let’s work through
and example to see this in action. First, let’s start with data provided below:
>> X = [0, 1, 2, 3, 4, 5, 6, 7];
>> Y = [5, 6, 6.5, 7, 8, 8.3, 9, 11];
Here y = f (x) and we would like to evaluate the integral:
7
Z
f (x) d x
0
In order to use integral, we need a function where for a specified value of x
it returns the corresponding value of y. One way for us to acheive this is using
interp1. Let’s consider the case of linear and cubic interpolation.
>> fun_linear = @(x) interp1(X,Y,x,'linear',NaN);
>> fun_cubic = @(x) interp1(X,Y,x,'pchip',NaN);
>> sol_linear = integral(fun_linear,0,7)
sol_linear = 52.8000
>> sol_cubic = integral(fun_cubic,0,7)
sol_cubic = 52.6833
Nice! Just to get an idea of how linear interpolation and cubic interpolation
are able to model the tabulated data, let’s make a plot:
>>
>>
>>
>>
hold on
plot(X,Y,'ko')
fplot(fun_linear,[0,7],'-k')
fplot(fun_cubic,[0,7],'-r')
13.2 Numerical Integration
491
11
10
9
8
7
6
5
0
1
2
3
4
5
6
7
Figure 13.6 The black circles are the tabulated data, the black line is the use
of linear interpolation, and the red line is the use of cubic interpolation.
Very cool. And just think, you could even combine this with fzero!
While this is relatively straightforward, know too that MATLAB has a built-in
function trapz which can be used to directly integrate tabulated data. And as
you might guess from the name, it use trapezoid method.
>> sol = trapz(X,Y)
sol = 52.8000
And as we would expect, this answer is exactly the same as the use of linear
interpolation. Does that make sense to you?
And that’s it. I trust that you are now prepared to take on any integrals in the
future, no matter how complicated. If you ever encounter multi-dimensional integrals, no worries. MATLAB still has you covered. Have a look at integral2 and
hrefhttps://www.mathworks.com/help/matlab/ref/integral3.htmlintegral3.
492
Chapter 13 Numerical Integration
13.3 Exercises
Exercise 13.1 The change in molar entropy of an ideal gas is given by the following
equation:
Z T2 ig
CP
P2
ig
ig
∆S ig = S 2 (T2 , P 2 ) − S 1 (T1 , P 1 ) =
d T − R ln
P1
T1 T
where T1 and T2 are the initial and final temperature, respectively, P 1 and P 2 are the
initial and final pressure, respectively, R is the molar gas constant, 8.314 J/(mol K), and
ig
C P is the constant pressure heat capacity which is given by the following equation:
ig
CP
R
= a0 + a1 T + a2 T 2 + a3 T 3 + a4 T 4
ig
Where T is in units of K. Know that C P /R is dimensionless; multiply by R in your favorite
ig
units to get C P . For methane, we have a 0 = 4.568, a 1 = −8.975 × 10−3 , a 2 = 3.631 × 10−5 ,
a 3 = −3.407 × 10−8 and a 4 = 1.091 × 10−11 .
1. Methane is available at T1 = 300 K and P 1 = 1 bar, and is to be compressed to
T2 = 500 K and P 2 = 100 bar. Compute ∆S ig .
2. Methane is available at T1 = 300 K and P 1 = 1 bar, and is isentropically compressed
(∆S ig = 0) to P 2 = 100 bar. Solve for T2 .
14
Chapter
Monte Carlo
In this chapter we will briefly discuss the topic of Monte Carlo sampling (or integration). Monte Carlo integration is a powerful tool to calculate integrals of
complicated, multidimensional functions. What do I mean by complicated and
multidimensional? My PhD research was in the area of statistical thermodynamics, and a major component of my PhD dissertation was developing strategies to
compute classical configurational integrals of fluid-phase systems. Imagine we
have a fluid-phase system consisting N Argon atoms:
Z
Z N = e −βU (~r ) d~
rN
V
where Z N is the classical configurational integral, U is the interaction energy
of the system which is a function of the location of every atom in the system,
β−1 = k B T , and the integral is over the coordinates of all of the atoms in the system.
What does this mean? The location of an atom, regardless of your coordinate
system, requires three coordinates, say x, y, and z. So if we have N atoms, this
corresponds to 3N independent variables. If we have just N = 10 atoms, this
corresponds to a function with 59,049 independent variables. These are the types
of problems that Monte Carlo integration is perfectly suited for. If you have a
simple one-dimensional (1-D) problem, stick to Simpson’s rule and the other
techniques you learned in you Calculus class. (If Simpson’s rule isn’t familiar,
what about the trapezoid method?)
So how does it work? Monte Carlo integration get’s it name from the gambling
city in Monaco, and is based on the concept of random sampling. It was developed
shortly after the time of World War II. At that time, the first supercomputers were
developed to help the war effort, and after the war, scientists were left with
some cool toys to experiment with. A group of scientists at Los Alamos National
Laboratory developed Monte Carlo integration as a tool to compute the classical
configurational integral of a hard sphere fluid. (Although the same method was
developed shortly before this time at Berkeley National Lab/UC Berkeley, but the
findings were never published.) The basic idea, as presented to me, was imagine I
493
494
Chapter 14 Monte Carlo
would like to compute the area of a pond. I have no way of measuring the area
of the pond directly. However, I can layout a rectangular bounding box around
the pond of known dimensions. I like rectangles because I can easily compute it’s
area. So this could be taken as first guess of the area of the pond, which is smaller.
The experiment then proceeds that I walk around the bounding box and
randomly throw rocks. If I hear a splash, the rock landed in the pond, it was a hit.
If I don’t hear a splash, then I hit land. I can then determine the fraction of the
rocks I randomly threw that hit the pond relative to the total number. Assuming I
randomly threw the rocks (and I threw an infinite number), this ratio should be
equal to the ratio of the area of the pond to the bounding box (the total area). So
if I multiply this number by the area of the bounding box, which is known, I get
an estimate of the area of the pond. Cool!
So let’s see it in practice and use it to numerically estimate a value of π. We
wish to compute the area of a circle with radius r = 1. The area of this circle is
A circle = πr 2 = π. However, π is unknown and is the quantity we wish to compute.
We will do so using Monte Carlo integration. To accomplish this, I center my
circle at the origin (0, 0), and then construct a bounding box with an edge length
of 2r . The area of this bounding box will be A = (2r )2 = 4. Let’s graph the circle
and bounding box:
>>
>>
>>
>>
>>
>>
>>
>>
hold on
Xs = [-1,1,1,-1,-1];
Ys = [1,1,-1,-1,1];
plot(Xs,Ys,'-k')
% Plotting the bounding box
theta = 0:pi/100:2*pi;
Xc = cos(theta);
Yc = sin(theta);
plot(Xc,Yc,'-r')
% Plotting the circle using 100 points
14.1 Random number generator basics
495
1
0.8
0.6
0.4
0.2
0
-0.2
-0.4
-0.6
-0.8
-1
-1
-0.5
0
0.5
1
Figure 14.1 Plot of circle with bounding box.
¡
¢
Now for the recipe. We will randomly sample x, y coordinates over the range
¡
¢
−1 ≤ x ≤ 1 and −1 ≤ y ≤ 1. Then for each x, y coordinate, we need to determine
if we are in or out of the circle. The equation of a circle of radius r = 1 and centered
at the origin is:
x2 + y 2 = 1
So, if x 2 + y 2 ≤ 1, we are inside the circle. We then determine the fractions (or
density, ρ) of hits relative to the total number of trials:
ρ=
Nhits
Ntrials
Then
A circle = ρ A = 4ρ = π
Great! Now let’s do it. Before doing so, let’s pause for a moment and briefly
discuss the generation of random numbers, which is the heart of any Monte Carlo
program.
14.1 Random number generator basics
Using the command
>> rand(1,n)
496
Chapter 14 Monte Carlo
we can generate a vector of length n containing numbers randomly distributed
between 0 and 1. Therefore
>> 100*rand(1,n)
will be a vector of numbers randomly distributed between 0 and 100, and
>> round(100*rand(1,n))
will be a vector of integers randomly distributed between 0 and 100. Lastly,
>> round(100*rand(1,n)-50)
will give a vector of integers randomly distributed between –50 and +50. (Note
that both the 100 and –50 could be included inside or outside the round function
with the same result.) So we can readily scale and shift our range. For our problem
of calculating π, we need random numbers between –1 and 1. This is therefore
accomplished as:
>> 2*rand(1,n)-1
When we use rand(1,n) to generate a vector of length n containing numbers
randomly distributed between 0 and 1, the numbers are uniformly distributed.
That is, all of the numbers between 0 and 1 may occur with equal probability.
There is an entire field dedicated to developing and testing random number generators. One key characteristic is the period. That is, how many unique numbers
the random number generator may generate before the string of numbers repeats.
If you are interested in these things, you should go speak with my friend Alan
Ferrenberg. One of his many claims to fame is pointing out issues related to
the period of common random number generators, which was of great interest
to state lotteries across the USA. Fortunately for us, MATLAB’s default random
number generator is the Mersenne Twister. The Mersenne Twister has a period of
219937 − 1; that’s a lot of numbers!
When MATLAB generates a sequence of random numbers, it is dependent
on the current value of the “seed”. Every time your restart MATLAB it begins
with a seed value of 0. So if each time you start MATLAB you were to execute
the command rand(1,10), you would obtain identical results. Rather than
restarting MATLAB, you can restore a seed value of 0 using either rng(0) or
rng(’default’). Note that each time you generate a new random number,
the seed value changes. Knowing this is beneficial because it allows you to reproduce your results and can be helpful for debugging. But if you want truly
random results, try setting your seed when you begin a new MATLAB session with
rng(’shuffle’). This will use a seed based on the current time, which is always
unique.
14.2 Back to π
497
Now let’s return to our regularly scheduled programming...
14.2 Back to π
Now that we know how to generate random numbers, let’s write a function to
compute π. You will notice that I do not write a for loop to loop over all of the
random samples. When the number of samples becomes large this becomes very
slow. I instead take advantage of MATLAB’s vector operations.
Listing 14.1 circle_area.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
%
%
%
%
Function to perform Monte Carlo simulation to estimate the value
of pi. This is accomplished by computing the area of a circle of
radius 1, for which the area is equal to pi. The bounding square
will be of area (2*r)^(2) = 4.
function res = circle_area(n)
% Generate a vector of random numbers of length n.
% We will have one vector corresponding to the x value and another
% corresponding to the y value.
%
% The numbers will be uniformly distributed between 0 and 1.
% We want numbers between -1 and 1. So first multiply by 2, so the
% numbers are between 0 and 2, then shift by -1 so they are between
% -1 and 1.
X = 2*rand(1,n)-1;
Y = 2*rand(1,n)-1;
end
% The equation of our circle is x^2 + y^2 = 1. Check to see if
% our x,y coordinates fall within the circle. (I.e., <= 1)
Circle = X.^(2) + Y.^(2);
% Performing a logical vector comparison.
Circle_hits = Circle <= 1;
% This is logical vector, so 1 for true and 0 for false. So if
% we sum the vector, that will give the number of hits. If we divide
% by the length of the vector (or n), that gives us the fraction that
% were hits. Since the area of the bounding square is (2*r)^(2)=4,
% multiply the fraction of hits by the area of the square to get
% the area of the cirle. Since r=1, the area of the circle
% is pi.
frac_hits = sum(Circle_hits)/n;
res = frac_hits*4;
Now let’s give it a try using 10, 1,000, 10,000, and 100,000 samples.
>> circle_area(10)
ans = 2
>> circle_area(1000)
498
Chapter 14 Monte Carlo
ans = 3.2160
>> circle_area(10000)
ans = 3.1332
>> circle_area(1000000)
ans = 3.1427
Cool! Not too bad. Remember,
>> pi
ans = 3.1416
As the number of trials increases, we appear to be getting closer and closer to
the correct result. Let’s write a script that loops over a range of trials and plots the
results. And as a reference, I will plot the value of π.
Listing 14.2 pi_convergence.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
% This script is a simple for loop to estimate values of pi using
% the function circle_area for a range of number of Monte Carlo trials.
% Number of trials
N = 10:10:1e5;
% Initialize our vector of estimates of pi
Sol = ones(1,length(N));
for i=1:length(N)
Sol(i) = circle_area(N(i));
end
hold on
plot(N,Sol,'-ko')
ylabel('estimate of pi')
xlabel('number of trials')
% Now plot as a reference y=pi
Ref = pi * ones(1,length(N));
plot(N,Ref,'-r')
14.2 Back to π
499
3.5
3.4
3.3
estimate of pi
3.2
3.1
3
2.9
2.8
2.7
2.6
0
2
4
6
number of trials
8
10
10 4
Figure 14.2 The value of π estimated using Monte Carlo integration versus
the number of samples.
We see that we converge rather quickly, but fluctuations about the mean persist.
Last, before moving on, I will update the function circle_area to plot the trials (or sample points). The updated function is below, followed by plots generated
by executing the function for the case of 10, 1,000, and 10,000 trials.
500
Chapter 14 Monte Carlo
Listing 14.3 circle_area_plot_trials.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
%
%
%
%
%
%
%
Function to perform Monte Carlo simulation to estimate the value
of pi. This is accomplished by computing the area of a circle of
radius 1, for which the area is equal to pi. The bounding square
will be of area (2*r)^(2) = 4.
In this version of the code I will the square, circle, and trial
points.
function res = circle_area_plot_trials(n)
% Generate a vector of random numbers of length n.
% We will have one vector corresponding to the x value and another
% corresponding to the y value.
%
% The numbers will be uniformly distributed between 0 and 1.
% We want numbers between -1 and 1. So first multiply by 2, so the
% numbers are between 0 and 2, then shift by -1 so they are between
% -1 and 1.
X = 2*rand(1,n)-1;
Y = 2*rand(1,n)-1;
% The equation of our circle is x^2 + y^2 = 1. Check to see if
% our x,y coordinates fall within the circle. (I.e., <= 1)
Circle = X.^(2) + Y.^(2);
% Performing a logical vector comparison.
Circle_hits = Circle <= 1;
% This is logical vector, so 1 for true and 0 for false. So if
% we sum the vector, that will give the number of hits. If we divide
% by the length of the vector (or n), that gives us the fraction that
% were hits. Since the area of the bounding square is (2*r)^(2)=4,
% multiply the fraction of hits by the area of the square to get
% the area of the cirle. Since r=1, the area of the circle
% is pi.
frac_hits = sum(Circle_hits)/n;
res = frac_hits*4;
hold on
% Plot the trials. We plot them first so that the circle will
% be printed on top.
plot(X,Y,'ko')
% Let's start by plotting the square
Xs = [-1,1,1,-1,-1];
Ys = [1,1,-1,-1,1];
plot(Xs,Ys,'-k')
% Next, we plot the circle
% Creating a vector of angles. Let's plot 100 points
theta = 0:pi/100:2*pi;
Xc = cos(theta);
Yc = sin(theta);
plot(Xc,Yc,'-r')
end
14.2 Back to π
501
1
0.8
0.6
0.4
0.2
0
-0.2
-0.4
-0.6
-0.8
-1
-1
-0.5
0
0.5
1
0.5
1
Figure 14.3 10 trials.
1
0.8
0.6
0.4
0.2
0
-0.2
-0.4
-0.6
-0.8
-1
-1
-0.5
0
Figure 14.4 1,000 trials.
502
Chapter 14 Monte Carlo
1
0.8
0.6
0.4
0.2
0
-0.2
-0.4
-0.6
-0.8
-1
-1
-0.5
0
0.5
1
Figure 14.5 10,000 trials.
14.3 Evaluating Integrals in General
The Monte Carlo method can be used to evaluate integrals in general. Our only
restrictions will be that:
1. the function is always positive
2. integral of the function is finite
Let’s consider the example of evaluating the integral of the function x 2 using
Monte Carlo integration over an arbitrary range, say a to b:
Z
I=
b
x 2d x
a
We need to begin by defining a bounding box of known area. For the case of x 2 , I
know that the function will be a maximum at either of the end points of the range
of x, a or b. So we begin by evaluating the function at the end points, finding the
maximum (y max ), and then define a bounding box of width b − a and height y max .
The area of the bounding box is A = y max (b − a).
¡
¢
Next, we will generate our random trials of x, y coordinates such that they
fall inside our bounding box. For this case, we will generate values of x over the
range a ≤ x ≤ b and values of y over the range 0 ≤ y ≤ y max . To determine if we
have a hit, we evaluate our function at each of our trial values of x, here x 2 , and
check to see if this value is less than or equal to our corresponding trial value of y
14.3 Evaluating Integrals in General
hits
(≤ y). If it is, it is a hit. As before, we compute the frequency of hits, ρ = NNtrials
. The
value of the integral (or area under the curve) is then found as I ≈ ρ A.
Nice! Below is a function I wrote to evaluate the integral of x 2 over a specified
range as just described. We will then evaluate its performance by comparing to
¢
Rb ¡
the analytic solution a = b 3 − a 3 /3.
Listing 14.4 xsq_area.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
% function [res,ref] = xsq_area(n,X_range)
%
% Function to compute the integral of x^2 using Monte Carlo integration
% Inputs: n, the number of trials
%
X_range, a vector of length 2 containing the range of
%
integration. The first element should be the lower
%
bound and the second value should be the upper bound.
% Outpusts: res, the integral as computed using Monte Carlo integration
%
ref, the analytic solutions
%
function [res,ref] = xsq_area(n,X_range)
% We need to size our bounding box. We know the smallest y value will be
% 0, and here we find the largest y value.
Y_ends = X_range.^(2);
Y_range = [0,max(Y_ends)];
% Since we know that ymax will occur at the upper bound in our x range,
% we could just as well use Y_ends(end) or Y_ends(2).
% Generate the x coordinate of our trials. X spans the range of
% X_range. Remember rand generates numbers uniformly over the range 0
% to 1. So first we multiple by the range of x values, and then shift
% by the lower bound.
X_trial = (X_range(2)-X_range(1))*rand(1,n)+X_range(1);
% Generate the y coordinate of our trials. We will generate our y
% coorindates in the same way. While we could simplify this for this
% problem, we will write the expression in general.
Y_trial = (Y_range(2)-Y_range(1))*rand(1,n)+Y_range(1);
% Determine number of hits. This will be when the coordinates of the
% trial are less than f(x). In general we could create a function file
% for f(x), but here x^2 is easy enough.
N_hits = Y_trial <= X_trial.^(2);
% The fraction of trials that are hits
frac_hits = sum(N_hits)/n;
% Multiplying the fraction of hits by the area of the bounding box to
% get the integral (or area) of x^2.
res = frac_hits*(Y_range(2)-Y_range(1))*(X_range(2)-X_range(1));
end
% The analaytic solution for comparison
ref = (1/3)*(X_range(2)^(3)-X_range(1)^(3));
503
504
Chapter 14 Monte Carlo
>> [mc_int, ref_inf] = xsq_area(1e6,[-2,6])
mc_int = 74.7360
ref_inf = 74.6667
One million samples takes very little time to execute, and the Monte Carlo estimate differs from the reference value by just 0.07 or 0.09%.
14.4 Simplifying and Generalizing the Algorithm
The algorithm of the previous section is robust and will work in general when
we have a funciton that is positive. However, the need to set-up a bounding box
makes it challenging to generalize and automate the calculation for an arbitrary
function. And what about functions that are both positive and negative? Here we
will overcome this limitation.
Imagine we wish to evaluate the integral of the function f (x) over the range
a ≤ x ≤ b:
b
Z
I=
f (x) d x
a
Our random number generator samples from a uniform distribution. We
saw rand(1,n) generated a vector of length n of numbers uniformly distributed
between 0 and 1. This means each number will be observed with equal probability.
We could use our random number generator to sample values of x over the range
a ≤ x ≤ b as (b-a)*rand(1,n)+a. Since each number will be observed with
equal probability, we can write:
p (x) = C
where p (x) is the probability of observing a value of x, and C is a constant. If the
probability distribution is normalized, then:
Z
b
a
Z
p (x) d x =
b
a
C d x = C (b − a) = 1
For this to be true, it must be that:
p (x) =
1
b−a
Okay, so how does this help us out. The key is that b − a is a constant. So we
can re-write our original integral as:
14.4 Simplifying and Generalizing the Algorithm
Z
I=
b
f (x) d x
a
Z b
(b − a)
dx
− a)
(b
a
Z b
1
= (b − a)
f (x)
dx
(b − a)
a
Z b
f (x) p (x) d x
= (b − a)
=
f (x)
a
The integral in the last line is our definition of the expectation value of f (x).
Assuming a large number of trials (or samples, n), the expectation value may be
estimated as the arithmetic average of function:
Z
I=
b
a
n
­
® (b − a) X
f (x i )
f (x) d x = (b − a) f (x) ≈
n i =1
The larger the number of trials, n, the better the approximation. This results in a
much simpler algorithm:
1. Generate a vector of uniformly distributed random numbers over the range
of interest (a ≤ x ≤ b). This corresponds to the trials or random samples of
x.
2. Evaluate the function f (x) for each value of x from the first step.
3. Compute the arithmetic average of the function f (x) evaluated at each trial
of x.
This results in a much simpler function that may readily be generalized. Let’s
apply this new algorithm to again integrate x 2 .
505
506
Chapter 14 Monte Carlo
Listing 14.5 xsq_area_simple.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
% function [res,ref] = xsq_area_simple(n,X_range)
%
% Function to compute the integral of x^2 using Monte Carlo integration
% Inputs: n, the number of trials
%
X_range, a vector of length 2 containing the range of
%
integration. The first element should be the lower
%
bound and the second value should be the upper bound.
% Outpusts: res, the integral as computed using Monte Carlo integration
%
ref, the analytic solutions
%
function [res,ref] = xsq_area_simple(n,X_range)
% Generate the x coordinate of our trials. X spans the range of
% X_range. Remember rand generates numbers uniformly over the range 0
% to 1. So first we multiple by the range of x values, and then shift
% by the lower bound.
X_trial = (X_range(2)-X_range(1))*rand(1,n)+X_range(1);
% Evaluate our function at the x trial values
Y = X_trial.^(2);
% Estimate the integral using MC
res = sum(Y)./n*(X_range(2)-X_range(1));
end
% The analaytic solution for comparison
ref = (1/3)*(X_range(2)^(3)-X_range(1)^(3))
>> [mc_int, ref_inf] = xsq_area(1e6,[-2,6])
mc_int = 74.5899
ref_inf = 74.6667
Nice! What is beautiful as compared to the previous version of my code is
I no longer need to worry about the bounding box and trying to determine the
maximum y value. This readily lends itself to being generalized and eliminating
the restriction that out function be positive. Rather than writing a specific code
for a particular function, let’s generalize the code by allowing the user to pass a
function handle for whatever function they would like integrated. Before doing
so, let me again remind you of our one restrictions:
1. the integral of the function is finite
14.4 Simplifying and Generalizing the Algorithm
Listing 14.6 monte_carlo_int.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
% function res = monte_carlo_int(fcn_handle,n,X_range)
%
% Function to compute the integral of a function using Monte Carlo
% integration. The method is restricted to functions that are positive,
% and the integral must be finite.
% Inputs: fcn_handle, function handle for the function you wish to
%
integrate. Note that for this code to work, the
%
function must be vectorized.
%
n, the number of trials
%
X_range, a vector of length 2 containing the range of
%
integration. The first element should be the lower
%
bound and the second value should be the upper bound.
% Outpusts: res, the integral as computed using Monte Carlo integration.
%
function res = monte_carlo_int(fcn_handle,n,X_range)
% Generate the x coordinate of our trials. X spans the range of
% X_range. Remember rand generates numbers uniformly over the range 0
% to 1. So first we multiple by the range of x values, and then shift
% by the lower bound.
X_trial = (X_range(2)-X_range(1))*rand(1,n)+X_range(1);
% Evaluate our function at the x trial values
Y = fcn_handle(X_trial);
% Estimate the integral using MC
res = sum(Y)./n*(X_range(2)-X_range(1));
end
>> xsq = @(X) X.^(2);
>> mc_int = monte_carlo_int(xsq,1e6,[-2,6])
mc_int = 74.7077
>> mc_int = monte_carlo_int(xsq,1e8,[-2,6])
mc_int = 74.6560
This agrees with our previous result. You must appreciate the beauty of this.
With just three lines of code, we can estimate the integral of any function (with a
single independent variable), no matter how complicated. While the advantage
of using Monte Carlo integration over conventional methods may not be clear for
functions with a single variable, the method really shines for multi-dimensional
integrals.
Before wrapping-up, let’s us consider a well known function that is known to
be both positive and negative. Let’s consider:
2π
Z
0
sin (x) d x = 0
>> fun = @(X) sin(X);
>> sol = monte_carlo_int(fun,1e6,[0,2*pi])
sol = -0.0018
507
508
Chapter 14 Monte Carlo
>> sol1 = monte_carlo_int(fun,1e6,[0,pi])
sol1 = 1.9989
>> sol2 = monte_carlo_int(fun,1e6,[pi,2*pi])
sol2 = -2.0001
where I have also split the integral to show the ability to evaluate the integral of
both positive and negative functions.
14.5 Exercises
Exercise 14.1 Write a script or function to demonstrate the use of monte_carlo_int.m
for a function of your choice, and compare to built-in function integral.
Chapter
15
An Introduction to Symbolic
Calculations
15.1 Some Symbolic Variable Basics
Before we can get started, we first need to learn how to create a symbolic variable.
This is relatively straightforward. We just need the keyword syms followed by the
name of the variable (or variables) that you wish to be symbolic. For example:
>> syms x
With our symbolic variable, we can do things like create functions. Consider the
case of:
f (x) = 4x 4 − 2x + 3
>> f = 4*x^(4)-2*x+3
f = 4*x^4 - 2*x + 3
Here we have assigned the expression on the right hand side to the variable f.
And since x is a symbolic variable, f will be symbolic too. If you would like to
evaluate your function for a specific value of x, this can be accomplishd using
the subs function. The basic call to subs used here will use as inputs: (1) the
expression, (2) the symbolic variable that you would like to substitute a numerical
value for, and (3) the numerical value you would like to substitute. For example:
f (x = 3) = 4 · 34 − 2 · 3 + 3 = 321
>> f_3 = subs(f,x,3)
f_3 = 321
Nice! And not to be outdone, we can plot our function as we did before using
fplot.
>> fplot(f,[-1,1],'-k')
509
510
Chapter 15 An Introduction to Symbolic Calculations
9
8
7
6
5
4
3
-1
-0.5
0
0.5
1
Figure 15.1 fplot(f,[-1,1],’-k’)
Let’s consider also the case of:
g (x) =
ex
x
>> g = exp(x)/x
g = exp(x)/x
Next, let’s see what happens if we try to evaluate our function again at x = 3.
>> g_3 = subs(g,3)
g_3 = exp(3)/3
Here we obtain the exact answer expressed symbolically. But what if we wanted
a numerical value? This is readily accomplished with the function double. The
function double converts a symbolic variable to a (double precision) numerical
value. Notice in the workspace window that g_3 and f_3 are of type symbolic. We
can get a numerical value as:
>> g_3_numeric = double(g_3)
g_3_numeric = 6.6952
15.1 Some Symbolic Variable Basics
Figure 15.2 Snapshot of Workspace window.
By default Matlab will assume that your symbolic variable (or variables) can
take on both real and imaginary components. If you would like to restrict yourself
to reals only, for example, then you would add the following after creating your
symbolic variable:
>> assume(x, 'real')
Please consult the documentation page for assume. This can be very useful if you
are only interested in a certain range of values.
While so far we have created symbolic variables and assigned expressions
to symbolic variables, it is also possible to create symbolic functions. Symbolic
functions look and act just like a function you might see in your mathematics
course.
>> f(x) = 4*x^(4)-2*x+3
f(x) = 4*x^4 - 2*x + 3
>> f_3 = f(3)
f_3 = 321
>> g(x) = exp(x)/x
g(x) = exp(x)/x
>> g_3 = g(3)
g_3 = exp(3)/3
>> g_3_numeric = double(g_3)
g_3_numeric = 6.6952
Here f and g are symbolic functions as compared to symbolic variables.
511
512
Chapter 15 An Introduction to Symbolic Calculations
Figure 15.3 Snapshot of Workspace window.
Is it better to store the expression to a symbolic variable or to create a symbolic
variable? Honestly, at present the answer is unclear to me.
15.2 Limits
Matlab provides the function limit to calculate the limits of functions. In its basic
form limit takes three arguments: (1) the function, (2) the symbolic variable that
you would like to look at a limiting value of, and (3) the value of the limiting value,
which may include inf for ∞. Using standard notation, limit(f,x,inf) would
correspond to:
lim f (x)
x→∞
At times it may additionally be necessary to evaluate a directional limit. No
worries, Matlab can handle this case with an optional fourth argument ’right’
or ’left’.
Your Calculus textbook likely has an entire chapter devoted to computing
limits, and should be cosulted for further technical details. Here we will only be
concerned with the use of Matlab to evaluate limits, and will consider a series of
examples ranging from basic to more challenging to demonstrate the power of
Matlab and its simplicity of use.
15.2.1 Basic Limits
Let’s get warmed up by evaluating some basic limits. I will refer to a basic limit
as that of a well-behaved function that is continuous at the point c where the
limit is desired. The limit may therefore be found using direct substitution. Below,
we will take a look at some basic examples. For each example, I will display the
15.2 Limits
513
desired problem. This will be followed by actual Matlab input and output used. I
will start off by clearing variables and creating a symbolic variable x.
lim x
x→−4
>>
>>
>>
l1
syms x
assume(x,'real')
l1 = limit(x,x,4)
= 4
lim x 2
x→2
>> l2 = limit(x^2,x,2)
l2 = 4
x2 + x + 2
x→1
x +1
lim
>> l3 = limit((x^2+x+2)/(x+1),x,1)
l3 = 2
lim
x→2
p
x
>> l4 = limit(sqrt(x),x,2)
l4 = 2^(1/2)
>> l4_numeric = double(l4)
l4_numeric = 1.4142
where double was used to give a numerical result.
lim x · cos (x)
x→π
>> l5 = limit(x*cos(x),x,pi)
l5 = -pi
>> l5_numeric = double(l5)
l5_numeric = -3.1416
514
Chapter 15 An Introduction to Symbolic Calculations
15.2.2 Intermediate Forms and L’Hôpital’s Rule
Recall from your Calculus class that direct substitution does not work all of the
time. A common scenario occurs when direct substitution leads to an indeterminate of the form 0/0 or ∞/∞. These cases are called indeterminate because they
do not guarantee that a limit exists, nor do they indicate what the limit is, if one
does exist. In your Calculus class, you should have learned to solve such problems
using L’Hôpital’s rule. Such problems are solved effortlessly using Matlab, with no
need for user input beyond that used for basic limits. Let’s look at a few examples.
e 2x − 1
x→0
x
lim
>> l6 = limit((exp(2*x)-1)/x,x,0)
l6 = 2
lim
x→∞
ln x
x
>> l7 = limit( log(x)/x,x,inf)
l7 = 0
15.2.3 Directional Limits
Lastly, we will consider the case of directional limits. Directional limits may
be useful to determine if a function is continuous at a point c. If a function is
continuous at point c, the limit from the left and right should be equivalent. Let’s
consider a simple case. We would like to know if the function f (x) = |x| /x is
continuous at x = 0. To text, we will compute the limit from the right (positivedirection) and from the left (negative-direction).
>> l8_pos = limit(abs(x)/x,x,0,'right')
l8_pos = 1
>> l8_neg = limit(abs(x)/x,x,0,'left')
l8_neg = -1
We find that the function is not continuous at x = 0. We can generate a plot to
confirm this is the case.
>> fplot(abs(x)/x,[-1,1],'-k')
15.2 Limits
515
1
0.8
0.6
0.4
0.2
0
-0.2
-0.4
-0.6
-0.8
-1
-1
-0.5
0
0.5
1
Figure 15.4 fplot(abs(x)/x,[-1,1],’-k’)
Nice! And what would happen if a direction was not specified?
>> l8 = limit(abs(x)/x,x,0)
l8 = NaN
The limit does not exist!
15.2.4 Limits and Derivatives
The definition of the derivative of a function is in terms of limits. Namely, the
derivative of a function f at x is given by:
df
f (x + ∆x) − f (x)
= f 0 (x) = lim
∆x→0
dx
∆x
Or equivalently we will use the following notation where ∆x = h:
df
f (x + h) − f (x)
= f 0 (x) = lim
h→0
dx
h
Using Matlab, we can calculate the differential of a function using this definition and limit. Here I will start by clearing variables since we will need an
additional symbolic variable h. We will consider cases for which I can find the
differential in the front cover of my Calculus textbook.
d x
e = ex
dx
516
Chapter 15 An Introduction to Symbolic Calculations
>> syms x h
>> d1 = limit((exp(x+h)-exp(x))/h,h,0)
d1 = exp(x)
And what if you wanted to evaluate the derivative at a specific value?
>> d1_3 = subs(d1,x,3)
d1_3 = exp(3)
>> d1_3_numeric = double(d1_3)
d1_3_numeric = 20.0855
where here we considered the case of x = 3.
We can also work out derivatives in general.
d x
b = b x ln (b)
dx
>> syms b
>> d2 = limit( (b^(x+h)-b^(x))/h,h,0)
d2 = b^x*log(b)
15.3 Derivatives
While we have just shown that we can use limit to evaluate derivatives, this
is more easily accomplished using the function diff. In its basic form, diff
takes two input arguments: (1) the function and (2) the (symbolic) variable to
differentiate with respect to. We can also pass an optional third parameter, a scalar
to indicate the order of differentiation. Let’s demonstrate using a few examples. I
will again begin by clearing all variables.
d x
e = ex
dx
>> syms x
>> d1 = diff(exp(x),x)
d1 = exp(x)
d x
b = b x ln (b)
dx
>> syms b
>> d2 = diff(b^x,x)
d2 = b^x*log(b)
15.4 Indefinite and Definite Integrals
For an example of higher-order differentiation, I will consider an example for
which I can work out the solution analytically.
f (x) = x 3
d 3
x = 3x 2
dx
d2 3
d 2
x =3
x = 6x
2
dx
dx
d3 3
d
x =6
x =6
3
dx
dx
>> d2_1 = diff(x^3,x,1)
d2_1 = 3*x^2
>> d2_2 = diff(x^3,x,2)
d2_2 = 6*x
>> d2_3 = diff(x^3,x,3)
d2_3 = 6
Nice! Know that Matlab also works very nicely with symbolic functions, which
eliminates the need for subs.
>> f(x) = exp(x);
>> f(2)
ans = exp(2)
>> Df = diff(f,x)
Df(x) = exp(x)
>> Df(2)
ans = exp(2)
where note that x has already been defined as a symbolic variable. Instead of just
assigning exp(x) to a symbolic variable f as we did at the start of this chapter,
now we have a symbolic function with an explicit input of x.
15.4 Indefinite and Definite Integrals
We can compute both the indefinite and definite integral of a symbolic function
using the function int. To compute an indefinite integral int requires two inputs: (1) the function and (2) the variable you are integrating with respect to. To
compute a definite integral, two additional input variables are required, namely
the limits of integration.
Z
1
xd x = x 2 + c
2
517
518
Chapter 15 An Introduction to Symbolic Calculations
where c is our constant of integration.
2
Z
0
xd x =
¤
1 £ 2 ¤¯¯2 1 £ 2
x 0 = 2 − 02 = 2
2
2
>> i1 = int(x,x)
i1 = x^2/2
>> i1_d = int(x,x,0,2)
i1_d = 2
Of course too we could compute the definite integral by taking the difference in
the antiderivative evaluated at the two limits.
>> i2 = int(x,x)
i2 = x^2/2
>> i2_d = subs(i2,x,2) - subs(i2,x,0)
i2_d = 2
>> i3(x) = int(x,x)
i3(x) = x^2/2
>> i3_d = i3(2) - i3(0)
i3_d = 2
Cool! What if you knew the lower limit was 0, but wanted to consider various
values of the upper limit?
>> syms b
>> i4(b) = int(x,x,0,b)
i4(b) = b^2/2
>> i4(2)
ans = 2
Building off of this, what if you wished to solve for the value of b such that
b
Z
0
xd x = 11
For that, we can readily use Matlab’s symbolic solve function. The function
solve requires just two inputs: (1) the equation of interest, and (2) the symbolic
variable you wish to solve for. Before we see this in action, I would like to remind
you that in Matlab = corresponds to a variable assignment, while == corresponds
to equivalence.
>> bsol = solve(i4 == 11,b)
bsol =
15.5 More fun with solve
519
22^(1/2)
-22^(1/2)
>> bsol_numeric = double(bsol)
bsol_numeric =
4.6904
-4.6904
Excellent! Notice that with solve, I do not need to set-up the function so that it is
equal to 0 as we did with fzero or fsolve. Now what if we were told that b was
greater than 0? Well, we can use assume to help.
>> assume(b>0)
>> bsol = solve(i4 == 11,b)
bsol = 22^(1/2)
Know that I like the notation assume(b>0) as it can be used in general. However,
Matlab also has a keyword for this case: assume(b, ’positive’).
15.5 More fun with solve
Do you remember the quadratic formula? If the answer is “no” or you are unsure,
no worries, Matlab remembers. I will first clear my variables to start fresh, and
then give it a try. And for those of us that have forgotten:
ax 2 + bx + c = 0
p
−b ± b 2 − 4ac
x=
2a
>> clear variables
>> syms a b c x
>> f = a*x^2 + b*x + c == 0
f = a*x^2 + b*x + c == 0
>> qf = solve(f,x)
qf =
-(b + (b^2 - 4*a*c)^(1/2))/(2*a)
-(b - (b^2 - 4*a*c)^(1/2))/(2*a)
Nice! What if we wanted to evaluate the case of a = 4, b = 6, and c = 2?
>> qf_eval = subs(qf, [a,b,c], [4,6,2])
qf_eval =
-1
-1/2
520
Chapter 15 An Introduction to Symbolic Calculations
Here we were substituting multiple variables at once, so we list them all out as a
vector. Nice! We could have instead used a symbolic function. Try this:
>> qf(a,b,c) = solve(f,x)
qf(a, b, c) =
-(b + (b^2 - 4*a*c)^(1/2))/(2*a)
-(b - (b^2 - 4*a*c)^(1/2))/(2*a)
>> qf_eval = qf(4,6,2)
qf_eval =
-1
-1/2
Fantastic!
So far we have made solve look like an all start. It is important to show some
limitations. Consider the following equation from Exam 2:
cos (x) cosh (x) = 1
We were asked to solve for the first five positive roots. To limit our range of search,
we could plot and see over what range the first five roots occur over. We could
then use assume to set the range of x values. Here I will just arbitarily set the
range, and then could always update the upper limit (that is, the upper assume)
to change the range. I will start by clearing my variables in case I had made any
assumptions previously.
>> syms x
>> assume(x>0)
>> assume(x<20)
>> eq = cos(x)*cosh(x) == 1;
>> sol = solve(eq,x)
Warning: Unable to solve symbolically. Returning a numeric solution using
vpasolve.
> In solve (line 304)
sol = 0
So there you have it. No analytic solution exists, so it is solving numerically. And
when it solves numerically, just like before we get just one root at a time. To get a
different value, my search range needs to change. For example
>> assume(x>1)
>> sol = solve(eq,x)
Warning: Unable to solve symbolically. Returning a numeric solution using
vpasolve.
> In solve (line 304)
sol = 20.420352245626061090936411189313
15.6 Factor my polynomial please
15.6 Factor my polynomial please
Related to solve, Matlab can also be used to factor a polynomial. This can be
useful to both simplify your expression and to solve for the roots. The Matlab
function that can do this is appropriately names... factor. In its basic form used
here, factor will take two arguments: (1) the expression of interest, and (2) the
symbolic variable you would like to factor with respect to. Let’s consider the
following simple example:
x 2 − 2x − 8 = (x + 2) (x − 4)
>> f = x^2-2*x-8;
>> fs = factor(f,x)
fs = [ x + 2, x - 4]
Notice that our factors are provided as a vector.
Similar to factor, Matlab can also simplify your expression by collecting coefficients of powers of a variable of interest using the collect function. Here’s a
simple example provided by Matlab:
>> syms x y
>> coeffs_x = collect(x^2*y + y*x - x^2 - 2*x,x)
coeffs_x = (y - 1)*x^2 + (y - 2)*x
>> coeffs_y = collect(x^2*y + y*x - x^2 - 2*x,y)
coeffs_y = (x^2 + x)*y - x^2 - 2*x
The last “trick” I will mention is that Matlab can intelligently simplify an
expression of interest. Please have a look at the Matlab documentation for
simplify. It includes some interesting examples, including the ability to work
with units. Yes, units! Here are a couple of examples to give you an idea:
>> m = (x^2 + 5*x + 6)/(x + 2);
>> ms = simplify(m)
ms = x + 3
>> n = cos(x)^(2) + sin(x)^(2);
>> ns = simplify(n)
ns = 1
15.7 Solving systems of equations
We have seen how we can use solve to solve a single equation for a single unknown. This would be the same application as we we had for fzero and roots
when solving numerically. It should come as no surprise that solve can also handle systems of linear and non-linear equations too, where we had used fsolve
521
522
Chapter 15 An Introduction to Symbolic Calculations
and rref previously. To see this in practice, let’s revisit our first example from
Chapter 8:
y = −x − 3
x 2 + y 2 = 17
>> syms x y
>> eq1 = y == -x-3;
>> eq2 = x^2 + y^2 == 17;
>> [solx,soly] = solve([eq1,eq2],[x,y])
solx =
1
-4
soly =
-4
1
And just like that we get both solutions in a single call, no initial guess necessary.
Also, notice that we did not even need to re-arrange our equations into the form
of an error function. Nice!
Again, so far we have looked at rather “simple” problems using our symbolic
solver. Try to solve the following system of equations which you encountered on
Exam 2:
¡
¢
¡ ¢
sin x + y − cos y = 0.17
¡
¢
cos x − y + sin (x) = 1.8
15.8 Initial Value Ordinary Differential Equations
Solving initial value ordinary differential equations (ODEs) can be tough. If I have
a simple first order ODE such as:
df
=af
dt
No problem, the ODE is separable and I can readily separate and integrate. Beyond the separable case, if you are lucky, you can recast the ODE into its general
form, and then look-up the general solution. Fortunately for us, MATLAB can
symbolically solve ODEs too!
To demonstrate, I will resolve some of our previous examples we solved numericall. Let’s consider first the example just mentioned. I’ll start by clearing
variables and then finding the general solution:
>> clear variables
>> syms f(t) a
>> ode = diff(f,t) == a*f
ode(t) = diff(f(t), t) == a*f(t)
15.8 Initial Value Ordinary Differential Equations
>> fsol(t) = dsolve(ode)
fsol(t) = C3*exp(a*t)
First, note that with the syms command I have f(t). This creates the symbolic
function f, and additionally the symbolic variabl t since since f is a function of t.
Here I also have a, which is an arbitary parameter. The differential equation is
solved with the function dsolve.
If a was known, you could readily using subs to provide its value. To solve
for the constant of integration, here C3, you could use solve followed by subs.
Interestingly, for our first order ODE we need just know the value of our function
at some t , not necessarily t = 0. But what if for example we were told:
f (t = 0) = 100
>> syms C3
>> Ci = solve(fsol(0) == 100, C3)
Ci = 100
>> fsol_final(t) = subs(fsol,C3,Ci)
fsol_final(t) = 100*exp(a*t)
Notice that I needed to first needed to indicate that the constant of integration,
C3, was symbolic. We could just as well do all of this with a single dsolve call:
>> clear variables
>> syms f(t) a
>> ode = diff(f,t) == a*f
ode(t) = diff(f(t), t) == a*f(t)
>> ic = f(0) == 100
ic = f(0) == 100
>> fsol(t) = dsolve(ode,ic)
fsol(t) = 100*exp(a*t)
Could we solve our rat problem?
df
((t ) = a f (t ) [1 + sin (ωt )]
dt
Where a = 0.01, ω = 2π/365, and f (t = 0) = 2
>>
>>
>>
>>
>>
clear variables
syms f(t)
a = 0.01;
omega = 2*pi/365;
ode = diff(f,t) == a*f*(1+sin(omega*t))
523
524
Chapter 15 An Introduction to Symbolic Calculations
ode(t) = diff(f(t), t) == (f(t)*(sin((2*pi*t)/365) + 1))/100
>> ic = f(0) == 2;
>> sol(t) = dsolve(ode,ic)
sol(t) = 2*exp(t/100 - (73*cos((2*pi*t)/365))/(40*pi))*exp(73/(40*pi))
Incredible! When we solved numerically, we found that at 365 days we had 76.9530
rats. Let’s see if we get the same value.
>> sol365 = sol(365)
sol365 = 2*exp(73/(40*pi))*exp(73/20 - 73/(40*pi))
>> sol365_numeric = double(sol(365))
sol365_numeric = 76.9493
Amazing.
We can also use dsolve to solve higher-order ODEs. Remember when we
solved higher-order ODEs numerically, we first thad to re-write the equation as a
series of first order ODEs. That is not necessary here. Let’s consider our free fall
example.
d 2z
(t ) = z 00 (t ) = −g
d x2
where z (t = 0) = z 0 and z 0 (t = 0) = v 0 .
>> clear variables
>> syms z(t) g z0 v0
>> ode = diff(z,t,2) == -g
ode(t) = diff(z(t), t, t) == -g
>> ic1 = z(0) == z0;
>> Dz = diff(z,t);
>> ic2 = Dz(0) == v0;
>> zsol(t) = dsolve(ode,[ic1,ic2])
zsol(t) = z0 + t*v0 - (g*t^2)/2
Fantastic! Note that for the second initial condition, z 0 (t = 0) = v 0 , we first needed
to create a symbolic function for the differential of z with respect to t , so then we
could evaluate it at t = 0.
If you also wanted velocity, we could get it two ways. First, we could just
differentiate our solution for z (t ).
>> vsol = diff(zsol,t)
vsol(t) = v0 - g*t
The alternative is we could re-write our second-order ODE as a system of firstorder ODEs as we did when we solved numerically, and then solve.
z 0 (t ) = v (t ) v 0 (t ) = −g
15.9 Boundary Value Problems
525
where z (t = 0) = z 0 and v (t = 0) = v 0 .
>> clear variables
>> syms z(t) v(t) z0 v0 g
>> ode1 = diff(z,t) == v
ode1(t) = diff(z(t), t) == v(t)
>> ode2 = diff(v,t) == -g
ode2(t) = diff(v(t), t) == -g
>> ic1 = z(0) == z0;
>> ic2 = v(0) == v0;
>> [solv(t),solz(t)] = dsolve([ode1,ode2],[ic1,ic2])
solv(t) = v0 - g*t
solz(t) = z0 + (g*t^2)/2 + t*(v0 - g*t)
A perfect match!
15.9 Boundary Value Problems
When I taught mass transfer, boundary value problems were very common. Unfortunately, they are not always easy to solve. When we solved ODEs numerically,
we only considered initial value ODEs. This is partially because solving boundary
value problems numerically is challenging. Let’s consider the very simple example
for the case of the diffusion of dilute A through a thin film of B of thickness l ,
where the concentration of A is known at x = 0 and x = l .
d 2C A
=0
d x2
C A (x = 0) = C A,0
C A (x = l ) = C A,l
How would you go about solving this numerically? Think in terms of Eulers
method. We would first need to introduce an additional variable so that we could
re-write our ODE as a system of two first-order ODEs.
dC A
dN
=N
=0
dx
dx
Thinking in terms of Eulers method, to solve we would need to know the value
of both C A and N at x = 0. We know the value of C A at x = 0, but not N . To
solve, we can apply what’s called the shooting method. Essentially, we solve our
system of ODEs for various values of N (x = 0) until we find the value such that
we satisfy the requirement C A (x = l ) = C A,l . Fancier methods exist, and MATLAB
has the functions bvp4c and bvp5c that you can use. However, these are iterative
schemes, so success may be dependent on the quality of your initial guess of the
solution.
526
Chapter 15 An Introduction to Symbolic Calculations
Here we will see how we can readily solve boundary value problems symbolic,
exactly as we solved initial value ODEs. For our diffusion example:
>> clear variables
>> syms cA(x) l cA0 cAl
>> ode = diff(cA,x,2) == 0;
>> b0 = cA(0) == cA0;
>> bl = cA(l) == cAl;
>> solC(x) = dsolve(ode,[b0,bl])
solC(x) = cA0 - (x*(cA0 - cAl))/l
This is the analytic solution we would have obtained if we have solved by hand.
We can solve harder cases too. Let’s again consider the case of the diffusion
of dilute A through a thin film of B , but now let’s assume that A undergoes a
homogeneous (bulk volumetric) reaction, where we will assume the reaction is
first order in A, r A = −kC A . This leads to the following at steady state:
k
d 2C A
− ◦ CA = 0
d x2
D AB
C A (x = 0) = C A,0
C A (x = l ) = C A,l
where D ◦AB is the diffusion coefficient of dilute A in B .
>>
>>
>>
>>
>>
>>
clear variables
syms cA(x) l cA0 cAl k DAB
ode = diff(cA,x,2) - (k/DAB)*cA == 0;
b0 = cA(0) == cA0;
bl = cA(l) == cAl;
solC(x) = dsolve(ode,[b0,bl])
solC(x) = (exp((x*(DAB*k)^(1/2))/DAB)*(cAl - cA0*exp(-(l*(DAB*k)^(1/2))/DAB))
(exp((l*(DAB*k)^(1/2))/DAB) - exp(-(l*(DAB*k)^(1/2))/DAB)) (exp(-(x*(DAB*k)^(1/2))/DAB)*(cAl - cA0*exp((l*(DAB*k)^(1/2))/DAB)))/
(exp((l*(DAB*k)^(1/2))/DAB) - exp(-(l*(DAB*k)^(1/2))/DAB))
This is great and correct, but it would really be nice if we could simplify this
expression at all. If we apply simplify here, the solution does not change. The
issue is we have terms of (DAB*k)ˆ
(1/2). Remember that the square root of a
negative number is imaginary. To make sure we are keeping it real, we can use
assume where we know that both quantities are positive.
>> assume(k>0)
>> assume(DAB>0)
>> solC(x) = simplify(solC)
solC(x) = -(cA0*exp((2*k^(1/2)*l)/DAB^(1/2)) - cAl*exp((k^(1/2)*l)/DAB^(1/2)
cAl*exp((k^(1/2)*(l + 2*x))/DAB^(1/2)))/(exp((k^(1/2)*x)/DAB^(1/2)) - exp((k^
15.9 Boundary Value Problems
That’s a big improvement! To shorten it up, let’s introduce α = k/D ◦AB . Note to get
it to work here, I need to use α = k 1/2 /D 1/2
.
AB
>> solC = subs(solC, k^(1/2)/DAB^(1/2), alpha)
solC(x) = -(cA0*exp(2*alpha*l) - cAl*exp(alpha*l) - cA0*exp(2*alpha*x) +
cAl*exp(alpha*(l + 2*x)))/(exp(alpha*x) - exp(alpha*(2*l + x)))
When solving by hand I could further simplify the equation and take advantage
of the relation that
¢
1¡
sinh (z) = e z − e −z
2
But try as I might with Matlab, I could not get it to simplify further. I was finally
able to get it by telling Matlab that x and l are both greater than 0, and also using
an additional agrument with simplify to increase the number of simplification
steps used.
>> assume(l>0)
>> assume(x>0)
>> solC(x) = simplify(solC,'Steps',100)
solC(x) = (cAl*sinh(alpha*x) + cA0*sinh(alpha*l - alpha*x))/sinh(alpha*l)
Beautiful!
527
Appendix
A
while loops
A.1 Loops: what we’ve done so far
Before discussing while loops, let us first review what we have done so far with
for loops. The general form of a for loop is:
for i=n0:n
statement
end
We enter the loop with i=n0, perform the statement, and then check to see if
i==n. If yes, we are done, exit the loop. If no, increment i by 1 and repeat. If
we wish to increment by a value other than 1, we can using the extended colon
operator:
for i=n0:dn:n
statement
end
where dn corresponds to the desired increment value. We have also seen that
we can exit a loop before the specified number of iterations using the break
statement. In Section 5.12 we were introduced to the continue statement. When
the continue statement is encountered, we jump immediately to the next loop
iteration.
A.2 while loops
Everything that we can do with a for loop we can also do with a while loop, and
with both we can use the break and continue statements. It is for this reason that
the while loop was not introduced sooner. First I wanted you to get comfortable
529
Chapter A while loops
530
with the for loop. Once you have mastered the for, there in principle is no need
for the while.
Choosing one over the other is a matter of programming style. When I was an
undergraduate student my research advisor told me never to use for loops, that
while loops were much better. As a graduate student, the philosophy of my group
was the opposite, for loops were preferred over while loops. So in addition to
your own preferred programming style, knowing both is good because a future
collaborator may have a different style than you.
After all that talk, let’s look at the structure of a while loop and a few examples
we have already encountered.
The basic structure of a while loop is:
while <conditional statement is true>
statement
end
This is best seen with an example. Let’s start by writing a function with a for loop
to sum the elements of a vector.
Listing A.1 sum_for.m
1
2
3
4
5
6
7
function res = sum_for(X)
c = 0;
for i=1:length(X)
c = c + X(i);
end
res = c;
end
We can write an equivalent function using a while statement as:
A.2 while loops
531
Listing A.2 sum_while.m
1
2
3
4
5
6
7
8
9
function res = sum_while(X)
c = 0;
i = 0;
while i < length(X)
i=i+1;
c=c+X(i);
end
res = c;
end
As compared to using a for loop, here we update our own index variable each
iteration. Notice that my conditional statement is i < length(X). You would
think it should be i <= length(X). But that would result in an error. MATLAB
will only check the conditional statement at the start of a new iteration, before we
update our index variable.
How my undergraduate advisor used to use while loops which did provide
some additional flexibility was to use a “flag” variable. Before the loop, the flag
variable is initialized with some value, say zero. Then when you want to stop/exit
the loop, you just change the value of flag to a different value. This gives flexibility when there are multiple different reasons to exit a loop without needing to
remember the break command. Here is how we could use a flag variable in our
summation example:
Listing A.3 sum_flag.m
1
2
3
4
5
6
7
8
9
10
11
12
13
function res = sum_flag(X)
c=0;
i=0;
flag = 0;
while flag == 0
i=i+1;
c=c+X(i);
if i == length(X)
flag = 1;
end
end
res = c;
end
All three functions yield identical results:
t Remember, the while statement executes while the
conditional statement is
true. You could therefore
just assign flag a value
of one initially, flag = 1.
Since to MATLAB a value
of 1 means true, your conditional statement could
be just flag. Then when
you want to exit, assign to
flag a value of zero, false.
To me, explicitly having a
logical statement of flag
== 1, while using more
characters, makes my code
easier to understand. And
if it is easier to understand,
I am less likely to make a
mistake.
Chapter A while loops
532
>> Y = 1:6;
>> sum_for(Y)
ans = 21
>> sum_while(Y)
ans = 21
>> sum_flag(Y)
ans = 21
All three loops and methods are identical. I encourage you to try them all and
decide which you prefer. Develop your own style.
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp
spsp
spsp spsp
spsp spsp spsp spsp spsp spsp
spsp spsp
spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp
spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp
spsp spsp spsp spsp spsp
spsp spsp
spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp
spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp
spsp
spsp spsp spsp spsp spsp
spsp spsp
spsp spsp spsp spsp
spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp
spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp
spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp
spsp spsp
spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp
spsp
spsp spsp spsp spsp spsp spsp
spsp spsp
spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp
spsp
spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp
spsp spsp
spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp
spsp
spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp spsp spsp
spsp spsp spsp spsp spsp spsp
Download