You are on page 1of 547

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 com-
putational 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 work-
shops 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 expe-
riences 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 ordi-
nary 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 Variables and values 1


1.1 A glorified calculator . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Math functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.5 Assignment statements . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.6 Why variables? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.7 Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.8 Floating-point arithmetic . . . . . . . . . . . . . . . . . . . . . . . . 10
1.9 Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.10 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.11 Chemical Engineering Examples . . . . . . . . . . . . . . . . . . . 15
1.12 MATLAB Online . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.13 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

2 Scripts 23
2.1 M-files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.2 Why scripts? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.3 The workspace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.4 More errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.5 Pre- and post-conditions . . . . . . . . . . . . . . . . . . . . . . . . 29
2.6 Assignment and equality . . . . . . . . . . . . . . . . . . . . . . . . 30
2.7 Updating variables . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.8 Incremental development . . . . . . . . . . . . . . . . . . . . . . . 35
2.9 Unit testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.10 Kinds of error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.11 Absolute and relative error . . . . . . . . . . . . . . . . . . . . . . . 38
2.12 CPB Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.13 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
2.14 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

3 for loops and basic plotting 49


3.1 for loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.2 plotting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.3 Sequences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

ix
x CONTENTS

3.4 Series . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.5 Generalization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.6 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.7 CPB Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
3.8 Creating multiple figures . . . . . . . . . . . . . . . . . . . . . . . . 73
3.9 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
3.10 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

4 Logic and vectors 81


4.1 Checking preconditions . . . . . . . . . . . . . . . . . . . . . . . . 81
4.2 if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.3 Relational operators . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.4 Logical operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.5 exist . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
4.6 Vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
4.7 Vector arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.8 Everything is a matrix . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.9 Indices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
4.10 Indexing errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
4.11 Vectors and sequences . . . . . . . . . . . . . . . . . . . . . . . . . 93
4.12 Sizing vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
4.13 Creating vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.14 Plotting vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
4.15 Reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
4.16 Apply . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
4.17 Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
4.18 Spoiling the fun . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
4.19 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
4.20 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
4.21 CPB Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
4.22 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
4.23 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

5 Functions 135
5.1 Name Collisions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
5.2 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
5.3 Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
5.4 Function names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
5.5 Multiple input variables . . . . . . . . . . . . . . . . . . . . . . . . 143
5.6 Logical functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
5.7 An incremental development example . . . . . . . . . . . . . . . . 149
5.8 Nested loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
5.9 Conditions and flags . . . . . . . . . . . . . . . . . . . . . . . . . . 151
5.10 Encapsulation and generalization . . . . . . . . . . . . . . . . . . . 153
5.11 A misstep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
CONTENTS xi

5.12 continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155


5.13 Mechanism and leap of faith . . . . . . . . . . . . . . . . . . . . . . 158
5.14 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
5.15 CPB Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
5.16 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
5.17 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170

6 Functions of vectors 173


6.1 Functions and files . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
6.2 Vectors as input variables . . . . . . . . . . . . . . . . . . . . . . . . 177
6.3 Vectors as output variables . . . . . . . . . . . . . . . . . . . . . . . 178
6.4 Multiple/optional output variables . . . . . . . . . . . . . . . . . . 180
6.5 Vectorizing your functions . . . . . . . . . . . . . . . . . . . . . . . 183
6.6 Sums and differences . . . . . . . . . . . . . . . . . . . . . . . . . . 185
6.7 Products and ratios . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
6.8 Existential quantification . . . . . . . . . . . . . . . . . . . . . . . . 190
6.9 Universal quantification . . . . . . . . . . . . . . . . . . . . . . . . 191
6.10 Logical vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
6.11 CPB Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
6.12 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
6.13 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201

7 Zero-finding 203
7.1 Why functions? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
7.2 Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
7.3 A note on notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
7.4 Nonlinear equations . . . . . . . . . . . . . . . . . . . . . . . . . . 205
7.5 Zero-finding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
7.6 fzero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
7.7 What could go wrong? . . . . . . . . . . . . . . . . . . . . . . . . . . 209
7.8 Finding an initial guess . . . . . . . . . . . . . . . . . . . . . . . . . 211
7.9 More name collisions . . . . . . . . . . . . . . . . . . . . . . . . . . 214
7.10 Debugging in four acts . . . . . . . . . . . . . . . . . . . . . . . . . 216
7.11 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
7.12 Functions: what we’ve done so far . . . . . . . . . . . . . . . . . . 221
7.13 Anonymous function . . . . . . . . . . . . . . . . . . . . . . . . . . 222
7.14 What could go wrong? . . . . . . . . . . . . . . . . . . . . . . . . . . 225
7.15 Saving anonymous functions . . . . . . . . . . . . . . . . . . . . . 226
7.16 Revisiting the duck . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
7.17 Numerical methods: zero-finding . . . . . . . . . . . . . . . . . . . 229
7.17.1 The bisection method . . . . . . . . . . . . . . . . . . . . . 229
7.17.2 The secant (or linear interpolation) method . . . . . . . . 237
7.18 roots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
7.19 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
7.20 CPB Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
xii CONTENTS

7.21 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249


7.22 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251

8 Systems of Equations 257


8.1 Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
8.2 Row and column vectors . . . . . . . . . . . . . . . . . . . . . . . . 260
8.3 The transpose operator . . . . . . . . . . . . . . . . . . . . . . . . . 261
8.4 Zero-finding: what we’ve done so far . . . . . . . . . . . . . . . . . 262
8.5 System of equations . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
8.6 What can go wrong? . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
8.7 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
8.8 One final fsolve note . . . . . . . . . . . . . . . . . . . . . . . . . . 273
8.9 System of linear equations . . . . . . . . . . . . . . . . . . . . . . . 274
8.10 Gaussian elimination . . . . . . . . . . . . . . . . . . . . . . . . . . 274
8.11 What could go wrong? . . . . . . . . . . . . . . . . . . . . . . . . . . 276
8.12 Left division . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
8.13 Application: Raoult’s Law . . . . . . . . . . . . . . . . . . . . . . . . 279
8.13.1 Specifying T and x 1 . . . . . . . . . . . . . . . . . . . . . . 280
8.13.2 Specifying p and x 1 . . . . . . . . . . . . . . . . . . . . . . 285
8.14 Application: CSTR . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
8.14.1 CSTR Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
8.14.2 A single reaction example . . . . . . . . . . . . . . . . . . . 294
8.15 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
8.16 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304

9 Ordinary Differential Equations 313


9.1 Differential equations . . . . . . . . . . . . . . . . . . . . . . . . . . 313
9.2 Euler’s method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
9.3 Another note on notation . . . . . . . . . . . . . . . . . . . . . . . . 316
9.4 ode45 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
9.5 Multiple output variables . . . . . . . . . . . . . . . . . . . . . . . . 319
9.6 Analytic or numerical? . . . . . . . . . . . . . . . . . . . . . . . . . 320
9.7 What can go wrong? . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
9.8 Stiffness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
9.9 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
9.10 Euler in action! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
9.11 Return of fzero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
9.12 Isothermal Batch Reactors . . . . . . . . . . . . . . . . . . . . . . . 349
9.12.1 Batch Reactor Basics . . . . . . . . . . . . . . . . . . . . . . 349
9.12.2 Useful Assumption . . . . . . . . . . . . . . . . . . . . . . . 351
9.12.3 Note on Units . . . . . . . . . . . . . . . . . . . . . . . . . . 351
9.12.4 Final Note . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352
9.13 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
9.14 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354
CONTENTS xiii

10 Systems of ODEs 355


10.1 Lotka-Voltera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
10.2 What can go wrong? . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
10.3 Output matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
10.4 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
10.5 Euler returns! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
10.6 Isothermal Batch Reactors: Multiple Reactions . . . . . . . . . . . 375
10.7 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378
10.8 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378

11 Second-order systems 383


11.1 Nested functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
11.1.1 Application fzero . . . . . . . . . . . . . . . . . . . . . . . 387
11.2 Newtonian motion . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
11.3 Freefall . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
11.4 Air resistance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397
11.5 Parachute! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405
11.6 Two dimensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
11.6.1 Flying in vacuum . . . . . . . . . . . . . . . . . . . . . . . . 409
11.6.2 Flying with air resistance . . . . . . . . . . . . . . . . . . . 419
11.7 What could go wrong? . . . . . . . . . . . . . . . . . . . . . . . . . . 427
11.8 ODE Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
11.9 What could go wrong? . . . . . . . . . . . . . . . . . . . . . . . . . . 440
11.10 Glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441
11.11 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488


13.2.2 Working with Tabulated Data . . . . . . . . . . . . . . . . . 490
13.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 492

14 Monte Carlo 493


14.1 Random number generator basics . . . . . . . . . . . . . . . . . . 495
14.2 Back to π . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497
14.3 Evaluating Integrals in General . . . . . . . . . . . . . . . . . . . . 502
14.4 Simplifying and Generalizing the Algorithm . . . . . . . . . . . . . 504
14.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508

15 An Introduction to Symbolic Calculations 509


15.1 Some Symbolic Variable Basics . . . . . . . . . . . . . . . . . . . . 509
15.2 Limits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512
15.2.1 Basic Limits . . . . . . . . . . . . . . . . . . . . . . . . . . . 512
15.2.2 Intermediate Forms and L’Hôpital’s Rule . . . . . . . . . . 514
15.2.3 Directional Limits . . . . . . . . . . . . . . . . . . . . . . . 514
15.2.4 Limits and Derivatives . . . . . . . . . . . . . . . . . . . . . 515
15.3 Derivatives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516
15.4 Indefinite and Definite Integrals . . . . . . . . . . . . . . . . . . . . 517
15.5 More fun with solve . . . . . . . . . . . . . . . . . . . . . . . . . . 519
15.6 Factor my polynomial please . . . . . . . . . . . . . . . . . . . . . 521
15.7 Solving systems of equations . . . . . . . . . . . . . . . . . . . . . . 521
15.8 Initial Value Ordinary Differential Equations . . . . . . . . . . . . 522
15.9 Boundary Value Problems . . . . . . . . . . . . . . . . . . . . . . . 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 t You may see different win-
building a strong foundational knowledge. From there, the MATLAB world is your dows and/or a different win-
dow layout upon starting
oyster. By the end of this chapter you will be able to:
MATLAB. The layout may
• Demonstrate the ability to perform basic mathematical operations in the be modified using the large
Layout icon on the Home
MATLAB command window
menu tab. You can also click
• Recall how to assign variables, order of operations, and how to search the on the windows and drag
them to re-arrange.
documentation

• Apply MATLAB to solve basic engineering problems t In teaching this course I


have used MATLAB ver-
If you work through the chapter and believe these goals are not met, please sions R2013a and R2018a.
re-review the material and reach out for help. 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 func-
runs the MATLAB interpreter, which allows you to type MATLAB commands, tions, all of which will not
then executes them and prints the result. impact this course. Please
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.

1
2 Chapter 1 Variables and values

Figure 1.1 MATLAB R2018a GUI.

Initially, the Command Window contains a welcome message: “New to MAT-


LAB? 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.
t If you are familiar using The welcome message is followed by a chevron:
a bash shell on Linux or
Apple OS (i.e., the default
>>
terminal), then you will
notice that those com-
mands work here in the which is the MATLAB prompt; that is, this symbol prompts you to enter a com-
Command Window . For exam- mand.
ple here, pressing will The simplest kind of command is a mathematical expression, which is made
recall your previous com- up of operands (like numbers, for example) and operators (like the plus sign, +).
mands. If you were to keep If you type an expression and then press Enter (or ), MATLAB evaluates the
pressing , it will keep go-
expression and prints the result.
ing through your previous
commands sequentially.
>> 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 be-
tween the expression and ans =, and between ans = and the result. In my exam-
ples I will leave it out to save paper.
The other arithmetic operators are pretty much what you would expect. Sub-
traction 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: paren- t Use parentheses, do NOT
theses, exponents, multiplication and division, and addition and subtraction. use brackets. Brackets
In the United States the acronym “PEMDAS” or the mnemonic phrase “Please mean something differ-
Excuse My Dear Aunt Sally” is commonly used to recall the order of operations. ent to MATLAB, and will be
Exponentiation happens before multiplication and division, followed by addition used later when working
with vectors and matrices.
and subtraction; if you want to override the order of operations, you can use
parentheses.
t When in doubt of the order
of operations, add paren-
>> 2 * (3-4) / 5 theses. This includes paren-
ans = -0.4000 theses around exponents.
I sometimes do this out of
When I added the parentheses I also changed the spacing to make the grouping of habit. If I wanted to raise 2
operands clearer to a human reader. This is the first of many style guidelines I will to the power of 10+6, I bet-
recommend for making your programs easier to read. Style doesn’t change what ter put 10+6 in parentheses.
Otherwise it will be 2 raised
the program does; the MATLAB interpreter doesn’t check for style. But human
to the power of 10, then the
readers do, and the most important human who will read your code is you.
result of that calculation
And that brings us to the First Theorem of debugging: plus 6.

Readable code is debuggable code. t There are so many possi-


ble function calls you may
It is worth spending time to make your code pretty; it will save you time debug- find it difficult to remember
ging! them all. If you can remem-
ber what the command
The other common operator is exponentiation, which uses the ^ symbol,
starts with, you can use
sometimes pronounced “carat” or “hat”. So 2 raised to the 16th power is Tab + Tab (or + ) to
try and auto-fill in the rest.
>> 2^16 If there is more than one
ans = 65536 possibility, a list of possible
commands will be shown. I
Again, exponentiation happens before multiplication and division, followed by often find this useful for lit-
tle details and spelling (e.g.,
addition and subtraction; if you want to override the order of operations, you can
sin or sine). (Again, this
use parentheses.
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:

>> sin(1)
Figure 1.2 If you need to find a ans = 0.8415
command, use the “Search Doc-
umentation” text box in the top This command is an example of a function call. The name of the function is
right corner of the MATLAB win- sin, which is the usual abbreviation for the trigonometric sine. The value in
dow, or search the documentation parentheses is called the argument. All the standard trig functions in MATLAB
online.
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 sepa-
rated 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 func- The inverse of exp is log, which computes the logarithm with base e:
tion log computes the log-
arithm with base e; what >> log(exp(3))
you likely think of as ln. ans = 3
To compute a logarithm
with base 10, use the func-
tion log10. You might also
This example also demonstrates that function calls can be nested; that is, you can
find the Change-of-Base use the result from one function as an argument for another.
Formula useful. 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
>> help sin a hyperlink. Give it a click.
Here, if you were to click
sin Sine of argument in radians.
on asin, it is as if you had
sin(X) is the sine of the elements of X. typed help asin.

See also asin, sind.


t If MATLAB does not sug-
gest a correction for a ty-
Reference page for sin pographical error, you
Other functions named sin may need to turn it on.
From the Home menu
Unfortunately, this documentation is not beginner-friendly. tab, select Preferences
One gotcha is that in previous versions of MATLAB1 , the name of the function Command Window . Then

appears in the help page in capital letters, but if you type it like that in MATLAB, under Display , you will find
a box that you can check
you get an error:
to “Suggest corrections for
mistyped functions and
>> SIN(1) variables.”
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.
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, One of the features that makes MATLAB more powerful than a calculator is the
not a variable. I only know ability to give a name to a value. A named value is called a variable.
this because if we type doc
MATLAB comes with a few predefined variables. For example the name pi
pi to bring up the docu-
mentation page, we find
refers to the mathematical quantity π, which is approximately
“pi returns the floating-
point number nearest the >> pi
value of π”. Since pi is a ans = 3.1416
function, it can be overrid-
den and used as a variable And if you do anything with complex numbers, you might find it convenient
name. This also means pi is
that both i and j are predefined as the square root of −1.
not stored in our Workspace .
But for now you can ignore
You can use a variable name anywhere you can use a number; for example, as
all this and pretend it is a an operand in an expression:
variable.
>> pi * 3^2
t Note: you can’t use Greek ans = 28.2743
letters in MATLAB; when
translating math expres- or as an argument to a function:
sions with Greek letters, it
is common to write out the >> sin(pi/2)
name of the letter (assum-
ans = 1
ing you know it).

>> exp(i * pi)


t Just like pi, i and j are tech- ans = -1.0000 + 0.0000i
nically functions. From
the documentation page,
“Since both i and j are As the second example shows, many MATLAB functions work with complex
functions, they can be numbers. This example demonstrates Euler’s identity: e i π + 1 = 0.
overridden and used as a Whenever you evaluate an expression, MATLAB assigns the result to a variable
variable. This permits you named ans. You can use ans in a subsequent calculation as shorthand for “the
to use i or j as an index in value of the previous expression”.
FOR loops, etc.”, which is a
common practice that we
>> 3^2 + 4^2
will use later in the text. But
as with pi, for now you can
ans = 25
ignore this.
>> sqrt(ans)
ans = 5

But keep in mind that the value of ans changes every time you evaluate an ex-
pression.
1.5 Assignment statements 7

1.5 Assignment statements


You can create your own variables, and give them values, with an assignment t Note: The current value of
statement. The assignment operator is the equals sign, =. all assigned variables will be
shown in the Workspace
>> x = 6 * 7 window, which is typi-
cally to the right of the
x = 42
Command Window , unless
you have modified your
This example creates a new variable named x and assigns it the value of the layout.
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 se-
quence 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.

>> fibonacci0 = 1;

>> LENGTH = 10;

>> first_name = 'allen'


first_name = allen

The first two examples demonstrate the use of the semi-colon, which sup-
presses 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
Figure 1.3 Workspace and His-
quotes is a string. tory windows. Notice variables
Although i, j and pi are predefined, you are free to reassign them. It is are listed in alphabetical and
common to use i and j for other purposes, but it is probably not a good idea to not chronological order in the
change the value of pi! Workspace window.

t In general, I will reserve


1.6 Why variables? variable names with upper-
case letters for vectors and
The most common reasons to use variables are: matrices, and will use lower-
case letters for scalars (i.e.,
1. To avoid recomputing a value that is used repeatedly. For example, if you everything else).
are performing computations involving e, you might want to compute it
once and save the result. t Spaces are not allowed, so
I commonly use an _ in
>> e = exp(1) place of where I might like
e = 2.7183 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

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:
t Two notes on breaking a
long expression onto mul-
>> r = 3
tiple lines using ...: 1) It
must follow after an operand r = 3
(i.e., +, –, *, /). 2) In the MAT-
LAB Command Window press- >> area = pi * r^2
ing Enter (or ) will cause area = 28.2743
MATLAB to evaluate the line
and will not have the desired
effect of continuing on to That way your code resembles the familiar formula πr 2 .
the next line. To do this, type
Shift + Enter . 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 computa-
tion. 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 multipli-
cation.

>> 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 t Again, when in doubt, use
argument as a string (rather than as an expression). In this case the sin function parentheses.
generates a reasonable error message, but in other cases the results can be baf-
fling. 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 π
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 vari- In mathematics, there are several kinds of numbers: integer, real, rational, irra-
ables are misleading; tional, imaginary, complex, etc. MATLAB only has one kind of number, called
floating-point numbers floating-point.
are sometimes, wrongly, You might have noticed that MATLAB expresses values in decimal notation.
called “real”.
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 which is only approximately correct. It’s not quite as bad as it seems; MATLAB
format by selecting the uses more digits than it shows by default. You can change the format to see the
Preferences icon, then se- other digits.
lecting Command Window .
There is a drop-down list >> format long
next to “Numeric format”
>> 1/3
under “Text display”.
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 t In MATLAB R2009a and ear-
expect, but if you are careful with how you use it, Inf can be quite useful. lier, dividing by zero and
For operations that are truly undefined, MATLAB returns NaN, which stands the log of zero would pro-
for “not a number”. duce a warning message
because it is usually consid-
ered undefined. A warning
>> 0/0
is like an error message, but
ans = NaN the computation is allowed
to continue.
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
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 com-
ments that provide additional information about the program. The percent
symbol % separates the comments from the code.

>> speed_of_light = 3.0e8 % meters per second


speed_of_light = 300000000

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 % position from the origin in meters


>> v = 100 % velocity in meters / second
>> a = -9.8 % 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.

x−µ 2
³ ´
− σp2
e
p (1.1)
σ 2π

Remember, you can’t use Greek letters in MATLAB; when translating math expres-
sions 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 recog-
nize 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

B
log10 p sat = A − (1.2)
T +C
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.

B
T= −C (1.3)
A − log10 p sat
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 abil-
ity 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 param-
eters, 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? t In this problem and likely
Let’s calculate it! in many of your classes, you
need to know the values of
The kinetic energy of a molecule may be taken to be independent of it’s potential
several constants and con-
(or configurational) energy. This allows us to calculate the average kinetic energy
version factors. In future
of a molecule as
chapters we will build-up
to using scripts and then
m 2 3
Ek = v = kB T (1.4) functions to solve a prob-
2 2 lem. 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 veloc-


ity, 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

q s
3k B T
speed = v2 = (1.5)
m

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
3RT
speed =
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 molec-
ular 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.
t Note that the built-in MAT-
You would like to know the temperature of superheated steam at 40 bar and with a
LAB function interp1 may
molar volume of v = 0.1 m3 /kg. In the “Superheated Steam” tables in the back of
be used to perform a linear
your CPB 314 text, you find the following data
interpolation as we are do-
ing here. However, we need
T (◦ C) v (m3 /kg)
to discuss vectors first before
600 0.0989
we can use it.
650 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
20 Chapter 1 Variables and values

X Y
x1 y1
x2 y2
x3 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 .
y3 − y1
y2 − y1 = (x 2 − x 1 ) (1.7)
x3 − x1
y3 − y1
y2 = (x 2 − x 1 ) + y 1 (1.8)
x3 − x1
You can simplify this expression as much or as little as you would like.
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 vari-
ables. 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!

>> v1 = 0.0989;
>> v2 = 0.1;
>> v3 = 0.1049;
>> t1 = 600;
>> t3 = 650;
>> t2 = (t3-t1)/(v3-v1)*(v2-v1)+t1
t2 = 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 Univer-
sity, our current licence includes access to MATLAB Online, which allows you
1.13 Glossary 21

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 Math-
Works account. As indicated on the main registration page, to access your orga-
nization’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 opera-
tions.

operand: A number or variable that appears in an expression along with opera-


tors.

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.
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 num-


ber).

floating-point: The kind of number MATLAB works with. All floating-point num-
bers 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 Figure 2.1 The New Script button.
You could also find “New Script” in
If you work through the chapter and believe these goals are not met, please the drop-down list from the New
re-review the material and reach out for help. 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 t There are also many other
are not writing more than a few lines. Beyond that, you will want to store your important reasons to write
program in a script and then execute the script. scripts, even “short” scripts.
A script is a file that contains MATLAB code. These files are also called “M- If there is a calculation you
files” because they use the extension .m, which is short for MATLAB. You can know you will need to per-
create and edit scripts with any text editor or word processor, but the simplest form many times, a script
can save you a great deal
way is by clicking the New Script button at the far left of the top menu under on
of time. Turning in a script
the Home tab. An Editor window appears running a text editor specially designed for a homework solution
for MATLAB. or lab report ensures that
others can reproduce your
work. It can also serve as a
lab notebook, documenting
exactly what you did.

23
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 Type the following code in the editor
Online, have a look back at
Section 1.12 and watch the x = 5
screen cast Introduction
to MATLAB Online from
and then press the large Save (outdated) floppy disk icon at the top left of the
Chapter 1: Variables and
values which includes an menu, or select the smaller Save icon in the smaller top right corner of your
example of downloading MATLAB desktop. You can also use your standard keyboard shortcuts, which for
myscript. 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.
t Just as with functions, Tab
By default, MATLAB will store your script in your current folder. When you run
+ Tab (or + ) works
when typing the name of the script from the Command Window , MATLAB will look in your current folder
script files too. If you can for the file, and it will also look in the search path, which is the list of directories
remember just the first few MATLAB searches for scripts. You can see the list of directories in the search path
letters, it will try to auto-fill by clicking on the “Set Path” icon on the Home tab. 1
the rest. If there is more Go back to the Command Window and type myscript (without the extension)
than one possibility, a list at the prompt. MATLAB executes your script and displays the result.
of possible commands will
appear. If you get an error,
this is a good check if you >> myscript
mis-spelled the name or are x = 5
in the wrong directory.
When you run a script, MATLAB executes the commands in the M-File, one after
t In MATLAB and in general, another, exactly as if you had typed them at the prompt.
a script is nothing more If something goes wrong and MATLAB can’t find your script, you will get an
than a series of commands error message like:
one could just as well enter 1 If you are curious, information about adding and removing folders to/from the search path
one after the other in a
may be found in the MATLAB documentation for “Change Folders on the Search Path.” You can
command prompt (or here
also add folders to the search path interactively using the “Set Path” icon on the Home tab. You
the Command Window).
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 some-
thing 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 MAT-
LAB’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 page 5 that when we typed
SIN, MATLAB suggested
Undefined function or variable 'my'.
it was a possible typo and
asked if we meant sin. If
The problem is that it is looking for a script named my. The problem is even a script (or function) with
worse if the first word of the file name is a function that exists. Just for fun, create a similar name is present
a script named abs val.m and run it. Remember, if you would like the effect of a in your path, MATLAB may
space in your file name, use an underscore (_) between abs and val. make a suggestion.
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
Figure 2.4 The large “Run” button
than “ans”, and I can make sure I pass any variables that need to be passed. But on the Home tab. Simply clicking
more on that later. For now, if you are curious, have a look at my Chapter 2: Run the Run button is equivalent to
Button screen cast. 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,
will provide you with a copy
and for i ≥ 3, F i = F i −1 + F i −2 . The elements of this sequence occur naturally in
of the example script file
many plants, particularly those with petals or scales arranged in the form of a
that I provide in the text.
logarithmic spiral.
The name of the file may be
found following the Listing The following expression computes the n th Fibonacci number:
heading. I will also link to
p !n à p !n #
an electronic copy of the file

1 1+ 5 1− 5
that you can download from Fn = p − (2.1)
5 2 2
my Google Drive.
Translate this expression into MATLAB and store your code in a file named fibonacci1.
t Note that on the Home tab, At the prompt, set the value of n to 10 and then run your script. The last line of
under Preferences MATLAB your script should assign the value of F n to ans.
Editor/Debugger , you will (ans = 55.0000)
find options available for
“Automatic file changes”. If Solution: (Link to screen cast with accompanying M-file.) Let’s start by writing the
you check the box “Save script. I will provide you with two examples. First, since the equation is not very
changes upon clicking away long, I will write everything on a single line. Second, I will break up the calculation
from a file”, your script (or into smaller, more manageable parts. The later approach is often preferred to
function) being edited will minimize the occurrence of errors due to syntax and also to make the code more
automatically be saved if readable and easier to debug. Also, note that I use ans = here only to make the
you click away from the file. script more readable; it makes it clear what the intended answer is. If we were to
This feature was activated omit this, ans = would still be printed to screen when we run the script; remember
by default when I started ans is the default variable MATLAB uses to store a result to when an alternative
MATLAB R2015a and R2018a name is not provided.
for the very first time.
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 s5 = sqrt(5);
2 t1 = (1 + s5) / 2;
3 t2 = (1 - s5) / 2;
4 dfib = t1^n - t2^n;
5 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: t Remember, you can change
the arrangement of your
• When you are writing more than a couple of lines of code, it might take windows using the Layout
a few tries to get everything right. Putting your code in a script makes it icon in the top menu bar.
easier to edit than typing it at the prompt.
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
script actually runs. Then
You must always be 100% sure that the code you are running is the
if problems arise, you can
code you think you are running. focus on the the code itself.

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.
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 Your variables are:
the Workspace window. To clear x z
a variable, you can highlight it by
clicking, then hit the Delete key on You can clear more than one variable at a time by listing the variables sequen-
your keyboard, or right-click with tially.
your mouse then select “Delete”.
If you right-click, you will also
find other useful commands that >> y = 7;
should be self-explanatory. >> clear x y
>> who

Your variables are:


z

If you wish to clear all of the variables, you could list all of the variables sequen-
tially, 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 29

(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):
30 Chapter 2 Scripts

fibonacci1.m
1 %
2 % Computing the nth Fibonacci number using equation 2.1
3 % Precondition: you must assign a value to n before
4 % running this script.
5 % Postcondition: the result is stored in ans.
6 %

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).

>> 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.
Figure 2.6 The Help window that
opens following the command doc
fibonacci1. 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 inter-
changeable. 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 31

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:
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 % Swap the current value of x with the current value of y
3 % Precondition: you must assign a value to x and y before running
4 % this script.
5 % Postcondition: the initial value of y is stored as the new value
6 % of x.
7 % the initial value of x is stored as the new value
8 % of y.
9 %
10
11 % Storing a copy of the intial value of y
12 y0 = y;
13
14 % Now re-assigning
15 y = x
16 x = y0
17
18 % And now clearing (or deleting) the new variable y0 that we created
19 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 loca-
tion 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 repeat-
edly, 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 % Update the number of cars in Albany and Boston from one weeek to the
3 % next.
4 % Precondition: you must assign the number of cars in Albany and
5 % Boston at the start of the week to a and b,
6 % respectively.
7 % Postcondition: a and b have been modified to reflect the number
8 % of cars that moved during the week.
9 %
10
11 % First we calculate the net number of cars gained/lost in Boston
12 % 5% or the cars in Albany (a) are dropped off in Boston (b)
13 % 3% of the cars in Boston (b) are dropped off in Albany (a)
14 atob = round(0.05*a) - round(0.03*b);
15 % Now updating the number of cars in each location
16 % (conservation of cars)
17 b = b + atob
18 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 sim-
plifying the computation:

atob = 0.05*a - 0.03*b


a = a - atob
b = b + atob
2.8 Incremental development 35

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.
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 compo-
nents 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 Oops! We forgot that the trig functions in MATLAB work in radians, not degrees.
use asind for 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 37

While this is one example of unit testing, you might also think of a large pro-
gramming 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 be-
cause 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 con-
fusing, 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 floating-
point 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.
38 Chapter 2 Scripts

2.11 Absolute and relative error


There are two ways of thinking about numerical errors, called absolute and rela-
tive.
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 % Calculating the vapor pressure of ethanol using Antione's equation
2 %
3 % Dr. Paluch, Example 2.4a
4 %
5 % Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
6 % oC) stored to variables a, b, and c, and the temperature
7 % in K stored to variable t_K.
8 % Post-condition: Psat in units of mmHg (p_mmHg), atm (p_atm),
9 % kPa (p_kPa), and bar (p_bar), and temperature in
10 % degrees C (t_C)
11 %
12
13 % First, let's convert T from oC to K
14 t_C = t_K-273.15;
15 % Calcutating log_p, with p in mmHg
16 log_p_mmHg = a-b/(t_C+c);
17 % p in mmHg
18 p_mmHg = 10^log_p_mmHg
19 % p in atm
20 p_atm = p_mmHg/760
21 % p in kPa
22 p_kPa = p_atm*101.325
23 % p in bar
24 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 % At a given pressure, calculate the correspnoding boiling point
2 % (or saturation temperature) using Antione's equation.
3 %
4 % Dr. Paluch, Example 2.4c
5 %
6 % Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
7 % oC) stored to variables a, b, and c, and the pressure
8 % in units of atm stored to p_atm.
9 % Post-condition: The saturation temperature in units of oC (t_C)
10 %
11 % Convert the inputed pressure from atm to mmHg
12 p_mmHg = p_atm*760;
13 % Calculating the saturation temperature
14 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 satura-
tion temperature, in this case 1 atm. Take note of the units used; I use units of
2.12 CPB Examples 41

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 equa-
tion 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 param-
eters 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.
42 Chapter 2 Scripts

Listing 2.7 Example_2_5.m


1 % At a given temperature, calculate the average speed of a molecule
2 % using the Maxwell-Boltzmann distribution.
3 %
4 % Dr. Paluch, Example 2.5
5 %
6 % Pre-condition: The temperature in oC stored to t_C, and the molecular
7 % weight of the molecule of interest in amu stored to mw.
8 % Post-condition: The average speed in units of m/s stored to speed_mps
9 %
10 % Convert the temperature from oC to K
11 t_K = t_C+273.15;
12 % Avogadro's number
13 navo = 6.022e23;
14 % Gas constant in J/mol K
15 r = 8.314;
16 % Boltzmann's constant, kB, in J/K
17 kB = r/navo;
18 % Calculating the mass of a molecule in kg
19 mass_kg = mw/navo/1000;
20 % Calculating the speed in m/s
21 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 su-
perheated 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) v (m3 /kg)
600 0.0989
650 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 % Perform linear interpolation on data of the form
2 %
3 % X | Y
4 % -----
5 % x1 | y1
6 % x2 | y2
7 % x3 | y3
8 %
9 % were we know all values of y2, which we wish to estimate. Note that
10 % x3>x2>x1.
11 %
12 % Dr. Paluch, Example 2.6
13 %
14 % Pre-condition: x1, x2, x3, y1 and y3
15 % Post-condition: y2
16
17 y2 = (y3-y1)/(x3-x1)*(x2-x1)+y1

For our problem, v corresponds to x, and T corresponds to y.


44 Chapter 2 Scripts

>> x1 = 0.0989;
>> x2 = 0.1;
>> x3 = 0.1049;
>> y1 = 600;
>> y3 = 650;
>> Example_2_6
y2 = 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 isola-


tion.

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 rec-
ommends 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
σ= (2.3)
2A
At the critical point, the two phases become one, and the vapor/liquid free energy bar-
rier 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 A × 102 B × 104 C × 106 D × 108 E × 1011 F × 1014 Tmin [K ] Tmax [K ]


propane 4.9138 −0.9349 −0.7665 0.4318 −1.1075 1.1416 86 369.82
ethanol 0.8673 6.6647 −4.7511 1.3783 −1.9592 1.1142 250 513.9
acetone 5.7452 0.4625 −1.4587 0.5045 −0.8216 0.5357 180 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” recom-
mends use of a modified Watson equation to calculate the enthalpy of vaporization as a
function of temperature:

T n
µ ¶
∆H vap = A 1 − (2.4)
B
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 A B n Tmin [K ] Tmax [K ]


ethanol 60.8036 516.25 0.380 300.00 516.25
ethane 21.3420 305.42 0.403 90.35 305.42
propane 26.8896 369.82 0.365 85.44 369.82
n-butane 33.0198 425.18 0.377 134.86 425.18
n-pentane 39.8543 469.65 0.398 143.42 469.65
n-hexane 45.61 507.43 0.401 177.84 507.43
n-heptane 49.73 540.26 0.386 182.56 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 n-
pentane? 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:

B
log10 S = A + +C log10 T (2.6)
T
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/M s
s= ¡ ¢ (2.7)
S/M s + 1 × 106 − S /M w
The molecular weight of water is 18.015 g/mol, and the molecular weight of the solutes
are provided in the table below.

compound Ms A B C Tmin [K ] Tmax [K ]


methane 16.043 41.2703870202563 −1055.8685134604 −14.6882884263499 273.15 360.95
ethane 30.070 −67.5454935533925 4033.20796737051 22.5339742887131 275.15 323.15
propane 44.097 −116.868391911852 6267.69431176702 39.4739999999982 283.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:

64 λ1 λ2
f = + ´+ (2.8)
Re 1 + exp τ1 −Re 1 + exp τ2 −Re ·
³ ³ ´
²
100 600 D

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. Re-

trieved 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

ρV D
Re =
µ
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 ¯
λ2 = ¯λ1 −
¯ ¯
1 ²
¡ ¢ ¯
¯ −2 log 10 ·
3.7065 D
¯

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 t Let’s interpret the for loop
the introduction of loops and plotting. By the end of this chapter you will be able another way. For the case
to: 1 ≤ i ≤ 52, execute the
command(s) between the
• Define a for loop and explain how it works for and end statements.
Initially, when we first en-
• Explain how to construct a for loop to automate repetitive calculations counter the for statement,
i is assigned a value of 1 (or
• Demonstrate the ability to construct basic plots
whatever integer you set the
• Apply the use of for loops and plotting to solve basic engineering problems lower-bound to be). Then
we execute car_update
If you work through the chapter and believe these goals are not met, please and reach end. Here, the
re-review the material and reach out for help. computer asks if i=52 (or
whatever integer you set
the upper-bound of the list
3.1 for loops to be). If i=52 we exit and
move on in the code. How-
A loop is a part of a program that executes repeatedly; a for loop is the kind of ever, if i<52, we go back to
loop that uses the for statement. for, increment the value of
i by one (i = i + 1), then
The simplest use of a for loop is to execute one or more lines a fixed num-
execute car_update, and
ber of times. For example, in Example 2.3 (page 33) we wrote a script named
then ask again if i=52. This
car_update (Listing 2.4) that simulates one week in the life of a rental car com- repeats until i=52 when the
pany. To simulate an entire year, we have to run it 52 times: end statement is reached.

for i=1:52 t We previously saw that


car_update adding a semi-colon (;) at
end the end of a line suppresses
MATLAB from printing the
The first line looks like an assignment statement, and it is like an assignment result to screen. A semi-
colon is not necessary here
statement, except that it runs more than once. The first time it runs, it creates the
for the for and end lines.
variable i and assigns it the value 1. The second time, i gets the value 2, and so
on, up to 52.

49
50 Chapter 3 for loops and basic plotting

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 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:
t When writing a for loop
from command line, you >> for i=1:5
do not need to press
i
Shift + Enter to get to the
next line. MATLAB recog-
end
nizes that the loop is “open”,
and will not evaluate until i = 1
you press Enter after the i = 2
end statement. Remember i = 3
MATLAB skips whitespace, i = 4
so I add spaces to make my
i = 5
command more readable.

Does the result make sense? This simple example allows us to test our under-
standing 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 (List-
ing 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 straight-
forward 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 % Update the number of cars in Albany and Boston over the
3 % course of 52 weeks.
4 % Precondition: you must assign the number of cars in Albany and Boston
5 % at the start of the week to a and b, respectively.
6 % Postcondition: a and b have been modified to reflect the number of
7 % cars that moved after 1 year (52 weeks).
8 %
9 for i=1:52
10 car_update
11 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.
52 Chapter 3 for loops and basic plotting

In the interest of checking our understanding, in Example 2.3 (page 33), we simu-
lated 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 % Update the number of cars in Albany and Boston over the
3 % course of 52 weeks.
4 % Precondition: you must assign the number of cars in Albany and Boston
5 % at the start of the week to a and b, respectively.
6 % Postcondition: a and b have been modified to reflect the number of
7 % cars that moved after 3 weeks.
8 %
9 for i=1:3
10 car_update
11 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 % Update the number of cars in Albany and Boston over the
3 % course of 52 weeks.
4 % Precondition: you must assign the number of cars in Albany and Boston
5 % at the start of the week to a and b, respectively.
6 % Postcondition: a and b have been modified to reflect the number of
7 % cars that moved after 3 weeks.
8 %
9 for i=1:3
10 disp('Week: ')
11 disp(i)
12 car_update
13 end

>> b = 150;
>> a = 150;
>> car_loop_short2
3.2 plotting 53

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
54 Chapter 3 for loops and basic plotting

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 man-
ually 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])

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.
t We will learn later that the If you close the Figure Window you will need to start over again; you will need
argument provided to xlim to again execute hold on to plot more than one point (or data set). Alternatively,
and ylim is actually a (row)
to begin working with new data, you can keep the Figure Window open and just
vector of length 2.
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 % Update the number of cars in Albany and Boston over the
3 % course of 52 weeks and plot the results.
4 % Precondition: you must assign the number of cars in Albany and Boston
5 % at the start of the week to a and b, respectively.
6 % Postcondition: a and b have been modified to reflect the number of
7 % cars that moved after 1 year (52 weeks).
8 %
9
10 % I will start by clearing the graph in case anyting is already printed.
11 % Note that if a figure window is not already open, a blank figure
12 % window will appear.
13 clf
14 % Execute hold on to allow plotting more than one point.
15 % Note, I said previously if a figure was already open, clf would
16 % clear the existing figure. If we had previously executed hold on,
17 % we would not need to do so again. However, when in doubt,
18 % exectute hold on. If hold was already on, it will stay on.
19 hold on
20
21 for i=1:52
22 car_update
23 plot(i, a, 'ro')
24 plot(i, b, 'bd')
25 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:


56 Chapter 3 for loops and basic plotting

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
180 Boston

170
Number of Cars

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
Window, you can use File Save As... . You can choose the appropriate image for-
mat, 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:

>> savefig('example_32.fig')
58 Chapter 3 for loops and basic plotting

>> 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 el-
ement 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 % Calculating the value of Ai+1 = Ai/2 where A1 = 1 both directly
3 % and recurrently. The recurrent calculation will not be printed, but
4 % used to check the correctness of the direct calculations.
5 % Precondition: none
6 % Postcondition: at the conclusion of the loop a will contain the
7 % value of A10
8 %
9
10 % a1 for use in the direct calculations
11 a1 = 1;
12 % initial value of b for the recurrent calculations
13 b = 1;
14 %
15 for i=2:10
16 b = b/2;
17 a = a1*0.5^(i-1)
18 % Checking that both compute the same value
19 check = b - a
20 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
¡ 1 ¢i −1
where A i = A 1 2 . A for loop is a natural way to compute the value of this
series:

a1 = 1;
total = 0;
for i=1:10
a = a1 * 0.5^(i-1);
total = total + a;
end
ans = total
60 Chapter 3 for loops and basic plotting

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
calculating recurrently, we would need to specify a starting point, as we will see
in the next Example.
t When using a for loop to The way we are using total is sometimes called an accumulator; that is, a
compute a sum, the accu- variable that accumulates a result a little bit at a time. Before the loop we initialize
mulator will almost always
it to 0. Each time through the loop we add in the i th element. At the end of the
start with a value of 0. This
way when we add our first
loop, total contains the sum of the elements. Since that’s the value we were
term to the sum, the sum is looking for, we assign it to ans.
equal to the first term. Like-
wise, when we later use for
loops to compute continu- Example 3.4
ous products, we will start In this section, we wrote a for loop to compute the sum of the sequence (or series),
with a value of 1 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 % Calculating the value of the series of Ai from 1 to 10
3 % with each element of the sequence computed directly.
4 % Precondition: none
5 % Postcondition: at the conclusion of the loop total and ans
6 % will contain the value of the sum
7 %
8
9 a1 = 1;
10 total = 0;
11 for i=1:10
12 a = a1 * 0.5^(i-1);
13 total = total + a;
14 end
15 ans = total

Listing 3.7 series_recurrent.m


1 %
2 % Calculating the value of the series of Ai from 1 to 10 with
3 % each element of the sequence computed recurrently.
4 % Precondition: none
5 % Postcondition: at the conclusion of the loop total and ans
6 % will contain the value of the sum
7 %
8
9 a = 1;
10 % Start with total = a1
11 total = a;
12 % Then we begin the loop with a2
13 for i=2:10
14 a = a/2;
15 total = total + a;
16 end
17 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.
62 Chapter 3 for loops and basic plotting

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 >> format long
a single line like we have
>> n=10; series_direct
here. Multiple commands
separated by semi-colon
total = 1.99804687500000
can be placed on a single
line rather than a separate >> n=20; series_direct
line for each command. In total = 1.99999809265137
series_direct the final
sum in printed to screen; >> n=30; series_direct
this therefore must end a
total = 1.99999999813735
line.

>> 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 % Calculating the facotrial of a number n
2 %
3 % Pre-condition: n, the number you wish to compute the factorial of
4 % Post-condition: ans and n_fact will contain the value of the factorial
5 % of n
6 n_fact = 1;
7 for i = 2:n
8 n_fact = n_fact*i;
9 end
10 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.
64 Chapter 3 for loops and basic plotting

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:

∞ x 2i +1
(−1)i
X
sin (x) = (3.1)
i =0 (2i + 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 % Calculating sin(x) using a Taylor series expansion
2 %
3 % Pre-condition: x in radians, where x is what you would like to compute
4 % the sine of, and n, the number of terms in the Taylor
5 % series expansion
6 % Post-condition: The value of sin(x) using n terms in the series will
7 % be stored to taylor_ans, and the result using MATLAB's
8 % built in function will be stored to ans_matlab. I
9 % will also plot the value of sin(x) vs the terms in the
10 % sum.
11 %
12
13 % Start by clearing our figure
14 clf
15 % Don't clear my graph when I add a new point
16 hold on
17
18 % My Taylor series accumulator
19 taylor_ans = 0;
20
21 for i=0:n
22 % We will use 2*i+1 twice, so let's precompute
23 dum = 2*i+1;
24 % Computing the factorial of 2*i+1
25 % Note that when i = 0, it won't perform the for loop. This is okay
26 % since the factorial of 1 is 1.
27 n_fact = 1;
3.6 Examples 65

28 for j=2:dum
29 n_fact = n_fact*j;
30 end
31 % Calculating the ith Taylor term
32 taylor_term = (-1)^(i)*x^(dum)/n_fact;
33 % Updating my Talylor sum
34 taylor_ans = taylor_ans+taylor_term;
35 plot(i,taylor_ans,'ro')
36 end
37 % Labeling the plot axis
38 xlabel('n')
39 ylabel('sin(x)')
40
41 taylor_ans
42 ans_matlab = sin(x)

1.6

1.5

1.4

1.3
sin(x)

1.2

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..
66 Chapter 3 for loops and basic plotting

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 % Computing the nth Fibonacci number. For now, we will assume
2 % that n is greater than 2. Later in Chapter 4, we will find
3 % that we can use an if statement to check this condition.
4 %
5 % Pre-condition: n, the value of the desired Fibonacci number (n>2)
6 % Post-condition: ans and Fi contain the value of the nth Fibonacci number
7 %
8 F1 = 1; % F(i-2)
9 F2 = 1; % F(i-1)
10 for i=3:n
11 Fi = F2 + F1;
12 % now thinking about what I need for my next time through the
13 % loop (for i+1)
14 prev_1 = Fi; % current i will be i-1
15 prev_2 = F2; % current i-1 will be i-2
16 F1 = prev_2;
17 F2 = prev_1;
18 end
19 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 % Plotting the first 20 Fibonacci numbers
2 %
3 % Pre-condition: none
4 % Post-condition: plot of the first 20 Fibonacci numbers
5
6 % Clear figure
7 clf;
8 % Don't clear graph with each new point
9 hold on
10
11 F1 = 1; % F(i-2)
12 F2 = 1; % F(i-1)
13 plot(1,F1,'ro')
14 plot(2,F2,'ro')
15 for i=3:20
16 Fi = F2 + F1;
17 % now thinking about what I need for my next time throuh the
18 % loop (for i+1)
19 prev_1 = Fi; % current i will be i-1
20 prev_2 = F2; % current i-1 will be i-2
21 F1 = prev_2;
22 F2 = prev_1;
23 plot(i,Fi,'ro')
24 end
25 xlabel('i')
26 ylabel('F_i')
68 Chapter 3 for loops and basic plotting

7000

6000

5000

4000
Fi

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 % Plotting the vapor pressure of ethanol using Antione's equation
2 % from 0 to 100 oC
3 %
4 % Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
5 % oC) stored to variables a, b, and c.
6 % Post-condition: Plot of log10 p vs 1/T where T is in units of K
7 % and p is in units of mmHg
8 %
9
10 % Start by clearing our figure
11 clf
12 % Don't erase the graph when we add a new data set
13 hold on
14
15 % We need to calculate log10 p over the range 0 to 100 C.
16 % We will loop from 0 to 100 C in increments of 1 C.
17 % Each interation we will plot the results, where
18 % the temperature in converted to K before calculating
19 % the inverse.
20 for t_C = 0:100
21 % Calcutating log_p_mmHg, p in units of mmHg
22 log_p_mmHg = a-b/(t_C+c);
23 % Calculating 1/t with t in units of K
24 tinv = 1/(t_C+273.15);
25 % Plotting
26 plot(tinv,log_p_mmHg,'ro')
27 end
70 Chapter 3 for loops and basic plotting

28
29 % labeling the axis
30 xlabel('1/T [1/K]')
31 ylabel('log10 p/mmHg')
32
33 % plot title
34 title('Clapeyron plot for ethanol for 0 to 100 C')
35
36 % Last, let's get in the habit of saving and printing our graphs
37 savefig('ethanol_clapeyron_plot.fig')
38 print('-dpng', 'ethanol_clapeyron_plot')

>> a = 8.13484;
>> b = 1662.48;
>> c = 238.131;
>> clapeyron_plot

Clapeyron plot for ethanol for 0 to 100 C


3.5

3
log10 p/mmHg

2.5

1.5

1
2.6 2.8 3 3.2 3.4 3.6 3.8
1/T [1/K] ×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 % Plotting the average speed of water and ethanol molecules over the
2 % range 0 to 100 oC
3 %
4 % Pre-condition: None. I will hardcode everything since we
5 % are asked for a specific temperature range and
6 % the two species are specified.
7 % Post-condition: Plot of average molecular speed versus temperature
8 %
9
10 % Start by clearing our figure
11 clf
12 % Don't erase the graph when we add a new data set
13 hold on
14
15 % gas constant in J/(mol K)
16 r = 8.314;
17 % Molecular weight of water and ethanol in kg/mol
18 mw_water = 18/1000;
19 mw_ethanol = 46/1000;
20
21 % Looping over the range 0 to 100 C and calculating the
22 % average molecular speed. Note our Maxwell-Boltzmann
23 % equation uses K for temperature, so we will perform
24 % the conversion each iteration.
25 for t_C = 0:100
26 % Temperature in K
27 t_K = t_C + 273.15;
28 % Speed of water in m/s
29 speed_water = sqrt( 3*r*t_K/mw_water );
30 speed_ethanol = sqrt( 3*r*t_K/mw_ethanol );
31 % Plotting
72 Chapter 3 for loops and basic plotting

32 % When plotting, we will use the temperature in C, since


33 % that is what the problem statement asked for.
34 % Water then ethanol
35 plot(t_C,speed_water,'ro')
36 plot(t_C,speed_ethanol,'bx')
37 end
38
39 % labeling the axis
40 xlabel('T [C]')
41 ylabel('speed [m/s]')
42
43 % plot title
44 title('Average speed from Maxwell-Boltzmann')
45
46 % legend.
47 % Data is plotted water then ethanol
48 legend('water','ethanol')
49
50 % Last, let's get in the habit of saving and printing our graphs
51 savefig('maxwell_plot.fig')
52 print('-dpng', 'maxwell_plot')

>> maxwell_plot

Average speed from Maxwell-Boltzmann


750
water
700 ethanol

650

600
speed [m/s]

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 73

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 % Plotting the average speed of water and ethanol molecules over the
2 % range 0 to 100 oC
3 %
4 % Pre-condition: None. I will hardcode everything since we
5 % are asked for a specific temperature range and
6 % the two species are specified.
7 % Post-condition: Plot of average molecular speed versus temperature
8 %
9
10 % Start by creating two figure windows. We will plot water in
11 % Figure 1, and ethanol in Figure 2. We will need to initiate with
12 % hold on in both.
13 figure(1)
14 hold on
15
16 figure(2)
17 hold on
18
19 % gas constant in J/(mol K)
20 r = 8.314;
21 % Molecular weight of water and ethanol in kg/mol
22 mw_water = 18/1000;
23 mw_ethanol = 46/1000;
24
25 % Looping over the range 0 to 100 C and calculating the
26 % average molecular speed. Note our Maxwell-Boltzmann
74 Chapter 3 for loops and basic plotting

27 % equation uses K for temperature, so we will perform


28 % the conversion each iteration.
29 for t_C = 0:100
30 % Temperature in K
31 t_K = t_C + 273.15;
32 % Speed of water in m/s
33 speed_water = sqrt( 3*r*t_K/mw_water );
34 speed_ethanol = sqrt( 3*r*t_K/mw_ethanol );
35 % Plotting
36 % When plotting, we will use the temperature in C, since
37 % that is what the problem statement asked for.
38 % Water then ethanol.
39 % As mentioned earlier, we will plot water in Figure 1 and
40 % ethanol in Figure 2
41 figure(1)
42 plot(t_C,speed_water,'ro')
43 figure(2)
44 plot(t_C,speed_ethanol,'bx')
45 end
46
47 % labeling the axis
48 % We will repeat for each figure
49 figure(1)
50 xlabel('T [C]')
51 ylabel('speed [m/s]')
52
53 figure(2)
54 xlabel('T [C]')
55 ylabel('speed [m/s]')
56
57
58 % plot title
59 figure(1)
60 title('Average speed of water from Maxwell-Boltzmann')
61
62 figure(2)
63 title('Average speed of ethanol from Maxwell-Boltzmann')
64
65
66 % Last, let's get in the habit of saving and printing our graphs
67 figure(1)
68 savefig('water_maxwell_plot.fig')
69 print('-dpng', 'water_maxwell_plot')
70
71 figure(2)
72 savefig('ethanol_maxwell_plot.fig')
73 print('-dpng', 'ethanol_maxwell_plot')

>> maxwell_multi_plot
3.8 Creating multiple figures 75

Average speed of water from Maxwell-Boltzmann


720

700

680
speed [m/s]

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

430
speed [m/s]

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 run-
ning 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
76 Chapter 3 for loops and basic plotting

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 differ-
ent 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 previ-


ous 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 replac-


ing 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 ):

T
Tr = (3.2)
Tc

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 A × 102 B × 104 C × 106 D × 108 E × 1011 F × 1014 Tmin [K ] Tmax [K ]


propane 4.9138 −0.9349 −0.7665 0.4318 −1.1075 1.1416 86 369.82
ethanol 0.8673 6.6647 −4.7511 1.3783 −1.9592 1.1142 250 513.9
acetone 5.7452 0.4625 −1.4587 0.5045 −0.8216 0.5357 180 508.10

1 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
78 Chapter 3 for loops and basic plotting

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 − (3.3)
B
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 A B n Tmin [K ] Tmax [K ]


ethane 21.3420 305.42 0.403 90.35 305.42
propane 26.8896 369.82 0.365 85.44 369.82
n-butane 33.0198 425.18 0.377 134.86 425.18
n-pentane 39.8543 469.65 0.398 143.42 469.65
n-hexane 45.61 507.43 0.401 177.84 507.43
n-heptane 49.73 540.26 0.386 182.56 540.26
n-octane 59.0771 568.83 0.439 216.38 568.83

Write a script to compute ∆H vap for each compound at 300 K, and create a plot of
vap
∆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?

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:

B
log10 S = A + +C log10 T (3.4)
T
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 Ms A B C Tmin [K ] Tmax [K ]


methane 16.043 41.2703870202563 −1055.8685134604 −14.6882884263499 273.15 360.95
ethane 30.070 −67.5454935533925 4033.20796737051 22.5339742887131 275.15 323.15
propane 44.097 −116.868391911852 6267.69431176702 39.4739999999982 283.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. Re-

trieved from https://app.knovel.com/hotlink/toc/id:kpYHPAS006/yaws-handbook-properties/


yaws-handbook-properties
3.10 Exercises 79

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.
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 prob-
lems

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 t Without knowing it, we
correctly before the loop runs. For example, this loop computes the sum of the actually had this case in
first n elements of a geometric sequence: Exercise 3.6 in your nested
loop to compute the fac-
torial of 2*i+1. The inner
a1 = 1;
(nested) loop should be for
total = 0; k=2:(2*i+1), where the
for i=1:n outer loop should be for
a = a1 * 0.5^(i-1); i=0:n, where n is the high-
total = total + a; est term in the sum. In the
end first pass, when i=0, the in-
ans = total ner loop will be for k=2:1.
MATLAB therefor did not
perform the loop. It worked
It works for any positive value of n, but what if n is negative? In that case, you get: out correctly, because we
initialize our factorial vari-
able with a value of 1, and 1!
81 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 state-
ments if the conditions are met. In the previous example, we could write:
t Remember, MATLAB’s doc-
umentation is your friend. if n<0
Try help if and doc if.
ans = NaN
You can also do the same
for else and elseif.
end

The syntax is similar to a for loop. The first line specifies the condition we are
t While I will not use them
in the current version of interested in; in this case we are asking if n is negative. If it is, MATLAB executes
the text, know that MAT- the body of the statement, which is the indented sequence of statements between
LAB does have an error the if and the end. MATLAB doesn’t require you to indent the body of an if
function that can be used statement, but it makes your code more readable, so you should do it, and don’t
to throw an error and dis- make me tell you again.
play a message, and also a In this example, the “right” thing to do if n is negative is to set ans = NaN,
warning function that can
which is a standard way to indicate that the result is undefined (not a number).
be used to display warning
messages. 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 con-
dition 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 83

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 com-
pound 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:
84 Chapter 4 Logic and vectors

t Look carefully at the use of if n < 0


elseif here. If MATLAB statement 1
gets to the elseif state- elseif n < 1
ment, it is because n ≥ 0. If statement 2
statement 2 is performed,
else
it is because n < 1 and
the elseif statement was statement 3
true. Putting it all together, end
statement 2 is performed
if and only if 0 ≤ n < 1. If n is If n is less than 0, execute statement 1 and proceed to end. If n is not less than
an integer, it must be 0. 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 The operators that compare values, like < and > are called relational operators
“true” or “false”. This is a because they test the relationship between two values. The result of a relational
perfect case of using the operator is one of the logical values: either 1, which represents “true,” or 0, which
command line to check represents “false.”
your understanding (i.e.,
Relational operators often appear in if statements, but you can also evaluate
unit testing). Try something
simple like 5<2 and/or 5>2.
them at the prompt:
What is the result?
>> x = 5;
t You must use caution >> x < 10
when checking for equiv- ans = 1
alence with “==”. Re-
member, we have seen You can assign a logical value to a variable:
that MATLAB computes
sin(pi)=1.2246e-16. >> flag = x > 10
Due to numerical limita-
flag = 0
tions, it is not exactly 0.
Instead of using if x==0,
you might try instead if x A variable that contains a logical value is often called a flag because it flags the
> n-tiny && x < n+tiny, status of some condition.
where “tiny” is a small The other relational operators are <= and >=, which are self-explanatory, ==,
number which you deem for “equal,” and ~=, for “not equal.” (In some logic notations, the tilde is the
close enough to 0 for your
symbol for “not.”)
application, and “n” is the
Don’t forget that == is the operator that tests equality, and = is the assignment
value you are looking for.
There will certainly be times operator. If you try to use = in an if statement, you get a syntax error:
when we will use “==”, such
as when we are only dealing
only integers.
4.4 Logical operators 85

>> 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 % right for the wrong reason
ans = 1

But don’t be fooled!

>> x = 17
>> 0 < x < 10 % just plain wrong
ans = 1

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:


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
look at a basic example. I wish to compute ni=1 (i + 1)2 , where n is the number of
P

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 % Script to compute the sum of (i+1)^2 from 1 to n
2 % Precondition: n, the number of terms in the sum
3 % Post-condition: total, contains the value of the sum
4
5 total = 0
6 for i = 1:n
7 total = total+(i+1)^(2);
8 end
9
10 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 func-
tion. 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 docu-
mentation 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 % Script to compute the sum of (i+1)^2 from 1 to n
2 % Precondition: n, the number of terms in the sum
3 % Post-condition: total, contains the value of the sum
4
5 if exist('n','var')
6
7 total = 0;
8 for i = 1:n
9 total = total+(i+1)^(2);
10 end
11
12 total
13
14 else % n does not exist
15 disp('The pre-condition n is not defined')
16 total = NaN
17 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 89

>> X = 1:5
X = 1 2 3 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 t This gives us a deeper un-
remember what is a scalar and what is a vector. derstanding of how a for
loop works. MATLAB keeps
Just as with sequences, the numbers that make up the vector are called ele-
track of the loop iteration
ments. number. Each iteration the
loop index variable is set
equal to that corresponding
4.7 Vector arithmetic element of the specified
vector.
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.
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 is a function that builds a new matrix with the given number of rows (2) and
ones(1,3). MATLAB can columns (3), and sets all the elements to 1. Now let’s see what we’ve got.
also created a new matrix of
0’s, Try zeros(2,3). Both >> whos
are useful for creating a Name Size Bytes Class Attributes
new matrix of a given di-
mension and initializing it.
What is the result if you try scalar 1x1 8 double
ones(3)? A 3 by 3 “square” vector 1x5 40 double
matrix. Have a look at the matrix 2x3 32 double
documentation with the
help command. 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 t As we will see, we also have
now. elementwise division (./)
and elementwise exponenti-
ation (.ˆ).
4.9 Indices
t So far we have only used the
You can select elements of a vector with parentheses:
ones command to create
a matrix, which is not too
>> Y = 6:10 interesting. But to select
Y = 6 7 8 9 10 an element of a matrix,
we would have something
>> Y(1) like this: Y(3,4). The first
ans = 6 number is the row index,
and the second number is
the column index.
>> 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
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

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:
t length can also be used
with a matrix to find the for i=1:length(Y)
number of elements in a
Y(i)
dimension (along a row
or column). You can also
end
ask for the length of a sub-
set of a vector or matrix There. Now that will work for a vector of any length.
dimension. But more on
this later... or have a look at
the documentation if you 4.10 Indexing errors
are eager. And if you are
really eager, when working An index can be any kind of expression, but the value of the expression has to be
with matrices the related a positive integer, and it has to be less than or equal to the length of the vector. If
function size can be very it’s zero or negative, you get this:
useful.
>> Y(0)
Subscript indices must either be real positive integers or logicals.

“Subscript indices” is MATLAB’s longfangled way to say “indices.” “Real positive in-
tegers” 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:
t You will notice that within
Listing 4.3 fibonacci_seq_script.m
the Editor window that the
1 % Computing the Fibonacci sequence out to n nice folks at MATLAB cau-
2 % Precondition: n, the number of terms in the sequence tion you about assigning
3 % Postcondition: F, a vector for the Fibonacci sequence a value to the variable ans.
4 % where each element corresponds to the Why? Because when you
5 % term in the sequence. The final element
perform a MATLAB opera-
6 % is stored to ans.
tion and do not assign the
7
8 % Initializing the sequence result to a variable, MAT-
9 F(1) = 1; LAB will assign the result to
10 F(2) = 1; ans. So MATLAB is just re-
11 minding you so that you do
12 % Computing elements 3 to n not accidentally overwrite
13 for i=3:n it.
14 F(i) = F(i-1) + F(i-2);
15 end
16
17 % Assigning the value of the nth element to ans
18 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:
94 Chapter 4 Logic and vectors

Listing 4.4 fibonacci_seq_shift_script.m


1 % Computing the Fibonacci sequence out to n
2 % Precondition: n, the number of terms in the sequence
3 % Postcondition: F, a vector for the Fibonacci sequence
4 % where each element corresponds to the
5 % term in the sequence. The final element
6 % is stored to ans.
7
8 % Initializing the sequence
9 F(1) = 1;
10 F(2) = 1;
11
12 % Computing elements 3 to n
13 for i=1:n-2
14 F(i+2) = F(i+1) + F(i);
15 end
16
17 % Assigning the value of the nth element to ans
18 ans = F(n)

Either version is fine, but you have to choose one approach and be consis-
tent. 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 95

Solution: (Link to screen cast and accompanying M-file.) I will provide two ex-
amples. 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 % Compute the first n elements of the geometric seris A_i = A_{i-1}/2
3 % Precondition: n, the number of terms you wish to compute
4 % Postcondition: at the conclusion of the loop, the n elements will
5 % we stored to the vector A
6 %
7
8 % The first element is known and equal to 1
9 A(1) = 1;
10
11 % Computing elements 2 to n
12 for i=2:n
13 % I shifted the index by 1 and start my loop at 2.
14 A(i) = A(i-1)/2;
15 end
16
17 % Displaying all n elements. If n is large, you may wish to remove this.
18 A

Listing 4.6 geometic_series_elements_2.m


1 %
2 % Compute the first n elements of the geometric seris A_{i+1} = A_i/2
3 % Precondition: the value of n, how many terms you wish to compute
4 % Postcondition: at the conclusion of the loop, the n elements will
5 % we stored to the vector A
6 %
7
8 % The first element is known and equal to 1
9 A(1) = 1;
10
11 % Computing elements 2 to n
12 for i=1:n-1
13 % Notice here my loop only goes up to n-1 since I am computing
14 % the term i+1.
15 A(i+1) = A(i)/2;
16 end
17
18 % Displaying all n elements. If n is large, you may wish to remove this.
19 A
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 col-
umn, all of the elements that we “skipped” will be assigned a value of zero:

>> T(8,12) = 1
T = 1 12 5 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 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 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 exam-
ples 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. t Row and column vectors
The command begin:end will create a vector that goes from begin to end in in- will be a topic of conversa-
tion later. For now, we will
crements of 1. The values of begin and end can be anything. But remember that
only consider row vectors.
the increment will be 1. Let’s consider a few examples and see why I emphasize
this point.

>> 1:5
ans =
1 2 3 4 5

>> 1.1:5.8
ans =
1.1000 2.1000 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).
98 Chapter 4 Logic and vectors

>> 1:2:6
ans =
1 3 5

>> 1:1.5:6
ans =
1.0000 2.5000 4.0000 5.5000

>> 6:-1:1
ans =
6 5 4 3 2 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 99

>> R = [5 3 0]
R =
5 3 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 dou- Open 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 func-
tion 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 in-
tegers. 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 dis-
tributed 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

40
Fn

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

8.5
Y

7.5

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 t When overriding the default
(since it is plotted on the x-axis) and the second Y. There is nothing special about settings and specify the line
and/or symbol type and
these names; you could just as well plot X as a function of Y. MATLAB always treats
the color, MATLAB does not
the first vector as the “independent” variable, and the second as the “dependent” care about the order. Try for
variable (if those terms are familiar to you). example ’ro-’, ’r-o’, and
I should also point out that despite plotting multiple data points, five in this ’-or’. You should find they
case, I did not need to use the hold on command. The reason is that MATLAB all yield the same result.
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.
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 % Multiply all of the elements of a vector X together
2 %
3 % Precondition: X, vector containing the sequence of elements
4 % Postcondition: total_prod, the result of multiplying all of the elements
5 % of the vector X together
6 %
7
8 % Initializing the accumulator variable
9 total_prod = 1;
10
11 % Computing the continuous product
12 for i=1:length(X)
13 total_prod = total_prod*X(i);
14 end
15
16 % Displaying the final result
17 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. t Remember that X(i) refers
This kind of operation is called apply, because you apply the operation to each to a single element of the
vector X. We can therefore
element in the vector.
use ˆ and do not need .ˆ,
For example, the following loop computes a vector Y that contains the squares
although both would give
of the elements of X (assuming, again, that X is already defined). the same result.

for i=1:length(X) t Later on when we discuss


Y(i) = X(i)^2 elementwise operations,
end you will find we could just
instead do Y = X.ˆ2 and
get the same result. Further,
functions like sin operate
Example 4.3 elementwise. So in the
next example, we could
Write a loop that computes a vector Y that contains the sines of the elements of X.
replace the entire loop with
To test your loop, write a script that
Y=sin(X) and get the same
result.
(a) Uses linspace to assign to X a vector with 100 elements running from 0 to
2π (X = linspace(0,2*pi)).
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 % Plot sine over the range 0 to 2*pi
2 %
3 % Precondition: None
4 % Postcondition: Vector X will contain 100 elements from 0 to 2*pi,
5 % vector Y will contain the corresponding value of sine,
6 % and a plot of X vs Y will be produced
7 %
8
9 % Vector X containing angles from 0 to 2*pi
10 X = linspace(0,2*pi);
11
12 % Pre-size our vector Y that will contain the sine of X
13 Y = zeros(1,length(X));
14
15 for i=1:length(X)
16 Y(i) = sin(X(i));
17 end
18
19 % Plotting
20 clf; % clear figure, just in case
21 plot(X,Y)
22 xlabel('\theta [radians]')
23 ylabel('sin(\theta)')
24 xlim([0,2*pi])
25
26
27 % Finally, saving and printing the figure
28 savefig('sine_plot.fig')
29 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: t Try writing a for loop with
the extended version of the
colon operator. This gives
for i=1:length(X)
you the ability to write a
if X(i) == 0 for loop that increments
ans = i by a value other than 1 each
end iteration.
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.
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
t Instead of assigning –1 to it in ans. If there are no negative numbers, it should set ans to –1 (which is not a
ans if there are no negative legal index, so it is a good way to indicate the special case).
numbers, 0 would be an-
other excellent option. Not
Solution: (Link to screen cast with accompanying M-file.) Let’s start by creating
only is 0 not a valid element
an array of fake data.
index, but in the context of
logicals, 0 means false (or
>> X = 6:-1:-4;
not true).

The first negative number is –1 and it is the 8th element of the vector X.
Listing 4.9 negative_element.m
1 % Find the index of the first negative number in vector X
2 %
3 % Precondition: vector X
4 % Postcondition: ans will contain the index of the first negative number
5 % of vector X
6
7 % Initialize ans with value of -1, so if there is no negative number
8 % it will return this value.
9 ans = -1;
10
11 for i=1:length(X)
12 if X(i) < 0
13 ans = i
14 break
15 end
16 end

>> negative_element
ans = 8
4.18 Spoiling the fun 107

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 follow-
ing 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 con-
cepts 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
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 Bytes Class Attributes
test 1x5 10 char

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 com-
putes 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 % Computing the nth Fibonacci number and the ratio of
2 % consecutive numbers
3 %
4 % Pre-condition: n, the number of terms in the Fibonacci
5 % series
6 % Post-condition: Vector F will contain the Fibonacci
7 % number of each each element index,
8 % and F_ratio will contain the ratio of
9 % consecutive Fibonacci numbers F(i+1)/F(i),
10 % beginning with i = 2. We will also plot
11 % F_ratio to look for convergence.
12
13 % Since we know the final size of F and F_ratio, let's presize the vectors
14 F = zeros(1,n);
15 F_ratio = zeros(1,n);
16
17 % Initializing
18 F(1) = 1;
19 F(2) = 1;
20 F_ratio(1) = 0;
21 F_ratio(2) = F(2)/F(1);
22
23 for i=3:n
24 F(i) = F(i-1)+F(i-2);
25 F_ratio(i) = F(i)/F(i-1);
26 end
27
28 % Clear our figure
29 clf
30 % Plot the Fibonacci ratio
31 plot(F_ratio,'ro-')
32 ylabel('Fibonacci ratio')
33 xlabel('i')
34
35 % Saving and printing the figure
36 savefig('fib_ratio.fig')
37 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:

xi + σ y i − xi d t
¡ ¢
x i +1 = (4.1)
£ ¤
y i +1 = y i + x i (r − z i ) − y i d t (4.2)
¡ ¢
z i +1 = z i + x i y i − bz i d t (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 % Solving the certain famous system of differential equations using the
2 % equations and conditions provided in the problem.
3 %
4 % Pre-condition: None. I will use provided parameters/settings
5 % Post-condition: 3-dimensional plots of the results as a function of
6 % time-step. The numerical results will be stored to the
7 % vectors X, Y, and Z.
8
9 % Clearing the variables just in case, since X, Y, and Z, are popular
10 % choices, or in case you just ran the script with a different number of
11 % time-steps.
12 clear X Y Z;
13
14 % The number of timesteps
15 n = 10000;
16
17 % Since we know the final size of vectors X, Y and Z, let's pre-size them
18 X = zeros(1,n);
19 Y = zeros(1,n);
20 Z = zeros(1,n);
21
22 % Initial values
23 X(1) = 1;
24 Y(1) = 2;
25 Z(1) = 3;
26 % Parameters
27 sigma = 10;
28 b = 8/3;
29 r = 28;
30 % Time-step
31 dt = 0.01;
32
33 for i = 1:(n-1)
34 X(i+1) = X(i)+sigma*(Y(i)-X(i))*dt;
35 Y(i+1) = Y(i)+(X(i)*(r-Z(i))-Y(i))*dt;
36 Z(i+1) = Z(i)+(X(i)*Y(i)-b*Z(i))*dt;
37 end
38 % For the first part of the problem we are asked to dispaly the values
39 % when n <= 10
40 if n <= 10
41 disp(X)
42 disp(Y)
43 disp(Z)
44 end
45 % Only generate 1 of the plots. Uncomment 1 and comment the other
46 % to switch
47 %plot3(X,Y,Z)
48 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 % The logistic map. Note that I will save my files as
2 % logmap_*, where * will be 1, 2, or 3 for the first, second, and
3 % third bullet respectively.
4 %
5 % Pre-condition: None. I will hard-code everything
6 % Post-condition: Vector X will have the first 50 elements of the series.
7 % That is, it will contain the population for years 1 to
8 % 50, corresponding to the index number.
9
10 % Clearing the variable just in case since X is a popular choice. This is
11 % also a good idea in case you previously ran the script with a different
12 % value of n. I'm not sure if I have mentioned it before, but note that
13 % clear is another one of those commands where we do not need a ; to
14 % supress output... there isn't any to supress.
15 clear X
16
17 n = 50;
18
19 % Since we know the final size of X, let's pre-size the vector
20 X = zeros(1,n);
21
22 % Initial population
23 X(1) = 0.5;
24
25 % Combined rate for reproduction and starvation
26 r = 3.9;
27
28 for i=1:(n-1)
29 X(i+1)=r*X(i)*(1-X(i));
30 end
31
32 % Plotting the result
33 plot(X)
34 xlabel('i')
35 xlim([1,n])
36 ylabel('X')
37
38 % Saving and printing the figure
39 savefig('logmap_1.fig')
40 print('-depsc','logmap_1.eps')
114 Chapter 4 Logic and vectors

Listing 4.13 logmap_2.m


1 % The logistic map. Note that I will save my files as
2 % logmap_*, where * will be 1, 2, or 3 for the first, second, and
3 % third bullet respectively.
4 %
5 % Pre-condition: None. I will hard-code everything
6 % Post-condition: Vector X will have the first 50 elements of the series.
7 % That is, it will contain the population for years 1 to
8 % 50, corresponding to the index number. In this case, it
9 % will be the last value of r we consider in the vector.
10 % More imporant will be our plot of X for values of r
11
12 % Clearing the variable just in case since X is a popular choice. This is
13 % also a good idea in case you previously ran the script with a different
14 % value of n.
15 clear X
16
17 % Initial population
18 X(1) = 0.5;
19
20 % Combined rate for reproduction and starvation
21 % For part b, let's make r a vector of values from 2.4 to 4 in increments
22 % of 0.2 using the extended colon operator. Note I will use R instead of r
23 % because now it is a vector.
24 R = 2.4:0.2:4.0;
25
26 n = 50;
27
28 % Clear our figure
29 clf
30
31 % Don't erase points each time we call plot
32 hold on
33
34 % Looping over all values of r, then all years of interest.
35 % For each value of r, we will plot the result before moving on to the next
36 % value. Notice that in my plot command I do not specify the color. This
37 % way MATLAB will automatically change the color for each new set.
38 for j = 1:length(R)
39 for i=1:(n-1)
40 X(i+1)=R(j)*X(i)*(1-X(i));
41 end
42 plot(X)
43 end
44
45 % Now let's make the plot look pretty and save it too.
46 xlabel('i')
47 xlim([1,n])
48 ylabel('X')
49
50 % Saving and printing the figure
51 savefig('logmap_2.fig')
52 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 documen-
tation 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 % The logistic map. Note that I will save my files as
2 % logmap_*, where * will be 1, 2, or 3 for the first, second, and
3 % third bullet respectively.
4 %
5 % This version has been added to demonstrate the use of num2str to add a
6 % legend.
7 %
8 %
9 % Pre-condition: None. I will hard-code everything
10 % Post-condition: Vector X will have the first 50 elements of the series.
11 % That is, it will contain the population for years 1 to
12 % 50, corresponding to the index number. In this case, it
13 % will be the last value of r we consider in the vector.
14 % More imporant will be our plot of X for values of r
15
16 % Clearing the variable just in case since X is a popular choice. This is
17 % also a good idea in case you previously ran the script with a different
18 % value of n.
19 clear X
20
21 % Initial population
22 X(1) = 0.5;
23
24 % Combined rate for reproduction and starvation
25 % For part b, let's make r a vector of values from 2.4 to 4 in increments
26 % of 0.2 using the extended colon operator. Note I will use R instead of r
27 % because now it is a vector.
28 R = 2.4:0.2:4.0;
29
30 n = 50;
31
116 Chapter 4 Logic and vectors

32 % Clear our figure


33 clf
34
35 % Don't erase points each time we call plot
36 hold on
37
38 % Looping over all values of r, then all years of interest.
39 % For each value of r, we will plot the result before moving on to the next
40 % value. Notice that in my plot command I do not specify the color. This
41 % way MATLAB will automatically change the color for each new set.
42 %
43 % Notice my updated plot call. In this updated version we are naming the
44 % data set. Or you can essentially think of it as we are attaching the
45 % legend label to the set as we plot it.
46 for j = 1:length(R)
47 for i=1:(n-1)
48 X(i+1)=R(j)*X(i)*(1-X(i));
49 end
50 plot(X,'DisplayName',num2str(R(j)))
51 end
52
53 % Now let's make the plot look pretty and save it too.
54 xlabel('i')
55 xlim([1,n])
56 ylabel('X')
57
58 % We alreay attached the legend names to each data set. Now we just need to
59 % tell MATLAB to display the legend.
60 legend
61
62 % Saving and printing the figure
63 savefig('logmap_2b.fig')
64 print('-depsc','logmap_2b.eps')
4.20 Examples 117

Listing 4.15 logmap_3.m


1 % The logistic map. Note that I will save my files as
2 % logmap_*, where * will be 1, 2, or 3 for the first, second, and
3 % third bullet respectively.
4 %
5 % Pre-condition: None. I will hard-code everything
6 % Post-condition: Plot of steady state X versus r
7
8 % Clearing the variable just in case since X is a popular choice
9 clear X;
10 % Initial population
11 X(1) = 0.5;
12 % Combined rate for reproduction and starvation
13 % From part b, we found that for r = 4, the steady state population was 0.
14 % Then decreasing in increments of 0.1, the result is chaotic until we get to
15 % 2.9. Then the population quickly converges to a steady state value.
16 R = 0:0.01:2.99;
17
18 n = 50000;
19
20 % clear our figure
21 clf
22 % don't erase points each time we call plot
23 hold on
24
25 % Looping over all values of r, then all years of interest
26 for j = 1:length(R)
27 for i=1:(n-1)
28 X(i+1)=R(j)*X(i)*(1-X(i));
29 end
30 plot(R(j),X(n),'ro')
31 end
32
33 % Now let's make the plot look pretty and save it too.
34 xlabel('i')
35 ylabel('X')
36 % I will set the x-axis range to go from the value of the first to last
37 % element of R. The first element is R(1). But what about the last. Well,
38 % the current value of j is that of the last loop iteration, which was
39 % equal to length(R). So we could use j or length(R) for the index. Or as
40 % we will see in the future, we could use end.
41 xlim([R(1),R(j)])
42
43 % Saving and printing the figure
44 savefig('logmap_3.fig')
45 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 con-
ditions; 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 % Calculating the vapor pressure of ethanol using Antione's equation
2 %
3 % Dr. Paluch, Example 4.9a
4 %
5 % Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
6 % oC) stored to variables a, b, and c, and the temperature
7 % in K stored to variable t_K. Please also store Tmin and
8 % Tmax, the minimum and maximum temperature, in K, to
9 % variables t_K_min and t_K_max.
10 % Post-condition: Psat in units of mmHg (p_mmHg), atm (p_atm),
11 % kPa (p_kPa), and bar (psat_bar), and temperature in
12 % degrees C (t_C)
13 %
14
15 % Check that we are in the range of applicability
16 if t_K >= t_K_min && t_K <= t_K_max
17 % First, let's convert T from oC to K
18 t_C = t_K-273.15
19 % Calcutating logPsat, with Psat in mmHg
20 log_p_mmHg = a-b/(t_C+c);
21 % Psat in mmHg
22 p_mmHg = 10^log_p_mmHg
23 % Psat in atm
24 p_atm = p_mmHg/760
25 % Psat in kPa
26 p_kPa = p_atm*101.325
27 % Psat in bar
28 p_bar = p_kPa/100
29 % Else, use is no appropriate
30 else
31 disp('Your input temperature is outside the range of use.')
32 p_mmHg = NaN;
33 p_atm = NaN;
34 p_kPa = NaN;
35 p_bar = NaN;
36 end
120 Chapter 4 Logic and vectors

Listing 4.17 Example_4_9b.m


1 % At a given pressure, calculate the correspnoding boiling point
2 % (or saturation temperature) using Antione's equation.
3 %
4 % Dr. Paluch, Example 4.9b
5 %
6 % Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
7 % oC) stored to variables a, b, and c, and the pressure
8 % in units of atm stored to p_atm. Please also store Tmin and
9 % Tmax, the minimum and maximum temperature, in K, to
10 % t_K_min and t_K_max
11 % Post-condition: The saturation temperature in units of oC (t_C) and K
12 % (t_K)
13 %
14 % Convert the inputed pressure from atm to mmHg
15 p_mmHg = p_atm*760;
16 % Calculating the saturation temperature
17 t_C = b/(a-log10(p_mmHg))-c;
18 t_K = t_C + 273.15;
19 % Check that we are within the range of applicability
20 if t_K >= t_K_min && t_K <= t_K_max
21 % Note display is similar to disp, only it displays the variabale name too.
22 display(t_C)
23 display(t_K)
24 % Else, use is no appropriate
25 else
26 disp('Your input pressure is outside the range of use.')
27 t_C = NaN;
28 t_K = NaN;
29 end
4.21 CPB Examples 121

Listing 4.18 Example_4_9c.m


1 % Plotting the vapor pressure of ethanol using Antione's equation
2 % from 0 to 100 oC
3 %
4 % Dr. Paluch, Example 4.9c
5 %
6 % Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
7 % oC) stored to variables a, b, and c.
8 % Post-condition: Plot of log10 psat vs 1/T where T is in units of K
9 % and psat is in units of mmHg
10 %
11
12 % Start by clearing our figure, just in case
13 clf
14 % Clear/delete our vectors in case we previously ran with a different,
15 % larger temperature range
16 clear log_p_mmHg tinv t_C
17
18 % We need to calculated log psat over the range 0 to 100 C
19 for t_C = 0:100
20 % Calcutating logPsat, with Psat in mmHg
21 % Remember loop indices start at 1, so we need to shift up by 1
22 log_p_mmHg(t_C+1) = a-b/(t_C+c);
23 % Calculating 1/T with T in units of K
24 tinv(t_C+1) = 1/(t_C+273.15);
25 end
26
27 % plotting
28 plot(tinv,log_p_mmHg,'-ro')
29
30 % labeling the axis
31 xlabel('1/T [1/K]')
32 ylabel('log10 p/mmHg')
33
34 % plot title
35 title('Clapeyron plot for ethanol for 0 to 100 C')
36
37 % Saving and printing to file
38 savefig('Exercise_4_9c.fig')
39 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 vari-
able 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 % Plotting the vapor pressure of ethanol using Antione's equation
2 % from 0 to 100 oC. In this version of the code we will use
3 % a counter variable.
4 %
5 % Dr. Paluch, Example 4.9c
6 %
7 % Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
8 % oC) stored to variables a, b, and c.
9 % Post-condition: Plot of log10 psat vs 1/T where T is in units of K
10 % and psat is in units of mmHg
11 %
12
13 % Start by clearing our figure, just in case
14 clf
15 % Clear/delete our vectors in case we previously ran with a different,
16 % larger temperature range
17 clear log_p_mmHg tinv t_C
18
19 % Initialize our counter variable
20 n = 0;
21
22 % We need to calculated log psat over the range 0 to 100 C
23 for t_C = 0:100
24 % Increment the counter variable by 1 each loop iteration
25 n = n+1;
26 % Calcutating logPsat, with Psat in mmHg
27 log_p_mmHg(n) = a-b/(t_C+c);
28 % Calculating 1/T with T in units of K
29 tinv(n) = 1/(t_C+273.15);
30 end
31
32 % plotting
33 plot(tinv,log_p_mmHg,'-ro')
34
35 % labeling the axis
36 xlabel('1/T [1/K]')
37 ylabel('log10 p/mmHg')
38
39 % plot title
40 title('Clapeyron plot for ethanol for 0 to 100 C')
41
42 % Saving and printing to file
43 savefig('Example_4_9c_counter.fig')
44 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 % Plotting the vapor pressure of ethanol using Antione's equation
2 % from 0 to 100 oC. In this version of the code we will created a vector of
3 % temperatures, then loop over the vector indices.
4 %
5 % Dr. Paluch, Example 4.9c
6 %
7 % Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
8 % oC) stored to variables a, b, and c.
9 % Post-condition: Plot of log10 psat vs 1/T where T is in units of K
10 % and psat is in units of mmHg
11 %
12
13 % Start by clearing our figure, just in case
14 clf
15 % Clear/delete our vectors in case we previously ran with a different,
16 % larger temperature range
17 clear log_p_mmHg tinv t_C
18
19 % Creating our vector of temperatures in C. We can use the standard colon
20 % operator, or we could use linspace. Let's use the colon operator to be
21 % consistent with our previous solutions.
22 t_C = 0:100;
23
24 % Pre-size the over vectors used
25 log_p_mmHg = zeros(1,length(t_C));
26 tinv = zeros(1,length(t_C));
27
28
29 % Looping over our temperatures
30 for i=1:length(t_C)
31 % Calcutating logPsat, with Psat in mmHg
32 log_p_mmHg(i) = a-b/(t_C(i)+c);
33 % Calculating 1/T with T in units of K
34 tinv(i) = 1/(t_C(i)+273.15);
35 end
36
37 % plotting
38 plot(tinv,log_p_mmHg,'-ro')
39
40 % labeling the axis
41 xlabel('1/T [1/K]')
42 ylabel('log10 p/mmHg')
43
44 % plot title
45 title('Clapeyron plot for ethanol for 0 to 100 C')
46
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 % Plotting the vapor pressure of ethanol using Antione's equation
2 % from 0 to 100 oC. In this version of the code we will created a vector of
3 % temperatures, and then spoil all of the fun with vector operations.
4 %
5 % Dr. Paluch, Example 4.9c
6 %
7 % Pre-condition: A, B, and C (for Antione's equation which uses mmHg and
8 % oC) stored to variables a, b, and c.
9 % Post-condition: Plot of log10 psat vs 1/T where T is in units of K
10 % and psat is in units of mmHg
11 %
12
13 % Start by clearing our figure, just in case
14 clf
15 % Clear/delete our vectors in case we previously ran with a different,
16 % larger temperature range
17 clear log_p_mmHg tinv t_C
18
19 % Creating our vector of temperatures in C. We can use the standard colon
20 % operator, or we could use linspace. Let's use the colon operator to be
21 % consistent with our previous solutions. Notice in this version of the
22 % script I will update the variable names to be consistent with the
23 % convention of using capital letters to indicate vectors.
24 T_C = 0:100;
25
26 % Calculating logPsat, with Psat in mmHg using elementwise operations
27 Log_p_mmHg = a-b./(T_C+c);
28
29 % Calculating 1/T with T in units of K
30 Tinv = 1./(T_C+273.15);
31
32 % plotting
33 plot(Tinv,Log_p_mmHg,'-ro')
34
35 % labeling the axis
36 xlabel('1/T [1/K]')
37 ylabel('log10 p/mmHg')
38
39 % plot title
40 title('Clapeyron plot for ethanol for 0 to 100 C')
41
42 % Saving and printing to file
43 savefig('Example_4_9c_vector_2.fig')
44 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 % Plotting the average speed of water and ethanol molecules over the
2 % range 0 to 100 oC
3 %
4 % Dr. Paluch, Example 4.10
5 %
6 % Pre-condition: None. I will hardcode everything since we
7 % are asked for a specific temperature range and
8 % the two species are specified.
9 % Post-condition: Plot of average molecular speed versus temperature
10 %
11
12 % Start by clearing our figure
13 clf
14 % Need hold on because we will plot two sets of data
15 hold on
16
17 % gas constant in J/(mol K)
18 r = 8.314;
19 % Molecular weight of water and ethanol in kg/mol
20 mw_water = 18/1000;
21 mw_ethanol = 46/1000;
22
23 % Looping over the range 0 to 100 C and calculating the
24 % average molecular speed. Note our Maxwell-Boltzmann
25 % equation uses K for temperature, so we will perform
26 % the conversion each iteration.
27 for t_C = 0:100
28 % Temperature in K
29 t_K = t_C + 273.15;
30 % Speed of water in m/s
31 % Remember, vector indices start at 1, so we need to shift
32 % up by 1
33 speed_water(t_C+1) = sqrt( 3*r*t_K/mw_water );
34 speed_ethanol(t_C+1) = sqrt( 3*r*t_K/mw_ethanol );
126 Chapter 4 Logic and vectors

35 end
36
37 % Plotting
38 % When plotting, we will use the temperature in C, since
39 % that is what the problem statement asked for. We can
40 % easily create the temperature vector
41 t_C = 0:100;
42 % Water then ethanol
43 plot(t_C,speed_water,'-ro')
44 plot(t_C,speed_ethanol,'-bx')
45
46 % labeling the axis
47 xlabel('T [C]')
48 ylabel('speed [m/s]')
49
50 % plot title
51 title('Average speed from Maxwell-Boltzmann')
52
53 % legend.
54 % Data is plotted water then ethanol
55 legend('water','ethanol')
56
57 % Save and print
58 savefig('Example_4_10.fig')
59 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 % Plotting the average speed of water and ethanol molecules over the
2 % range 0 to 100 oC. In this version of the code I will use a loop
3 % counter variable.
4 %
5 % Dr. Paluch, Example 4.10
6 %
7 % Pre-condition: None. I will hardcode everything since we
8 % are asked for a specific temperature range and
9 % the two species are specified.
10 % Post-condition: Plot of average molecular speed versus temperature
11 %
12
13 % Start by clearing our figure
14 clf
15 % Need hold on because we will plot two sets of data
16 hold on
17
18 % gas constant in J/(mol K)
19 r = 8.314;
20 % Molecular weight of water and ethanol in kg/mol
21 mw_water = 18/1000;
22 mw_ethanol = 46/1000;
23
24 % Initializing our loop counter variable
25 n = 0;
26
27 % Looping over the range 0 to 100 C and calculating the
28 % average molecular speed. Note our Maxwell-Boltzmann
29 % equation uses K for temperature, so we will perform
30 % the conversion each iteration.
31 for t_C = 0:100
32 % Increment the loop counter variable by 1 each loop iteration
33 n = n+1;
34 % Temperature in K
35 t_K = t_C + 273.15;
36 % Speed of water in m/s
37 speed_water(n) = sqrt( 3*r*t_K/mw_water );
38 speed_ethanol(n) = sqrt( 3*r*t_K/mw_ethanol );
39 end
40
41 % Plotting
42 % When plotting, we will use the temperature in C, since
43 % that is what the problem statement asked for. We can
44 % easily create the temperature vector
45 t_C = 0:100;
46 % Water then ethanol
47 plot(t_C,speed_water,'-ro')
48 plot(t_C,speed_ethanol,'-bx')
128 Chapter 4 Logic and vectors

49
50 % labeling the axis
51 xlabel('T [C]')
52 ylabel('speed [m/s]')
53
54 % plot title
55 title('Average speed from Maxwell-Boltzmann')
56
57 % legend.
58 % Data is plotted water then ethanol
59 legend('water','ethanol')
60
61 % Save and print
62 savefig('Example_4_10_counter.fig')
63 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 temper-
ature 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 % Plotting the average speed of water and ethanol molecules over the
2 % range 0 to 100 oC. In this version of the code I define the temperature
3 % vector at the beginning, then loop over the vector indices. This will
4 % also allow us to pre-size all of our vectors.
5 %
6 % Dr. Paluch, Example 4.10
7 %
8 % Pre-condition: None. I will hardcode everything since we
9 % are asked for a specific temperature range and
10 % the two species are specified.
11 % Post-condition: Plot of average molecular speed versus temperature
12 %
13
14 % Start by clearing our figure
15 clf
16 % Need hold on because we will plot two sets of data
17 hold on
18
19 % Creating our vector of temperatures in C. Here I will use colon operator,
20 % but linspace would be another good option. Also, I will update the
21 % notation to use capital letters for vector variables.
22 T_C = 0:100;
23
24 % While we are at it, let's compute a vector of temperatues in K since we
25 % will need them in the calculations.
26 T_K = T_C + 273.15;
27
28 % Now we can pre-size all of our other vectors. Again, note the update to
29 % the variable names to coincide with the use of capital letters to
30 % indicate vectors.
31 Speed_water = zeros(1,length(T_C));
32 Speed_ethanol = zeros(1,length(T_C));
33
34
35 % gas constant in J/(mol K)
36 r = 8.314;
37 % Molecular weight of water and ethanol in kg/mol
38 mw_water = 18/1000;
39 mw_ethanol = 46/1000;
40
41 % Looping over the temperature range. We will look using vector indices. I
42 % will use T_K since temperature in K is needed for the calculations.
43 for i=1:length(T_K)
44 % Speed of water in m/s
45 Speed_water(i) = sqrt( 3*r*T_K(i)/mw_water );
46 Speed_ethanol(i) = sqrt( 3*r*T_K(i)/mw_ethanol );
47 end
130 Chapter 4 Logic and vectors

48
49 % Plotting
50 % When plotting, we will use the temperature in C, since
51 % that is what the problem statement asked for.
52 % Water then ethanol
53 plot(T_C,Speed_water,'-ro')
54 plot(T_C,Speed_ethanol,'-bx')
55
56 % labeling the axis
57 xlabel('T [C]')
58 ylabel('speed [m/s]')
59
60 % plot title
61 title('Average speed from Maxwell-Boltzmann')
62
63 % legend.
64 % Data is plotted water then ethanol
65 legend('water','ethanol')
66
67 % Save and print
68 savefig('Example_4_10_vector.fig')
69 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 % Plotting the average speed of water and ethanol molecules over the
2 % range 0 to 100 oC. In this version of the code I define the temperature
3 % vector at the beginning, and then perform the calculations using
4 % elementwise operations.
5 %
6 % Dr. Paluch, Example 4.10
7 %
8 % Pre-condition: None. I will hardcode everything since we
9 % are asked for a specific temperature range and
10 % the two species are specified.
11 % Post-condition: Plot of average molecular speed versus temperature
12 %
13
14 % Start by clearing our figure
15 clf
16 % Need hold on because we will plot two sets of data
17 hold on
18
19 % Creating our vector of temperatures in C. Here I will use colon operator,
20 % but linspace would be another good option. Also, I will update the
21 % notation to use capital letters for vector variables.
22 T_C = 0:100;
23
24 % While we are at it, let's compute a vector of temperatues in K since we
25 % will need them in the calculations.
26 T_K = T_C + 273.15;
27
28 % gas constant in J/(mol K)
29 r = 8.314;
30 % Molecular weight of water and ethanol in kg/mol
31 mw_water = 18/1000;
32 mw_ethanol = 46/1000;
33
34 % Now perform the calculations using elementwise operations
35 % Speed of water in m/s
36 Speed_water = sqrt( 3*r*T_K/mw_water );
37 Speed_ethanol = sqrt( 3*r*T_K/mw_ethanol );
38
39 % Plotting
40 % When plotting, we will use the temperature in C, since
41 % that is what the problem statement asked for.
42 % Water then ethanol
43 plot(T_C,Speed_water,'-ro')
44 plot(T_C,Speed_ethanol,'-bx')
45
46 % labeling the axis
47 xlabel('T [C]')
48 ylabel('speed [m/s]')
49
50 % plot title
51 title('Average speed from Maxwell-Boltzmann')
132 Chapter 4 Logic and vectors

52
53 % legend.
54 % Data is plotted water then ethanol
55 legend('water','ethanol')
56
57 % Save and print
58 savefig('Example_4_10_vector_2.fig')
59 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 logi-
cal 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 MAT-


LAB 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 133

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 applica-
bility 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.
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 To define a new function, you create an M-file with the name you want, and put a
if statements, there is no function definition in it. For example, to create a function named myfunc, create
need for a semicolon after
an M-file named myfunc.m and put the following definition into it.
the signature line.
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 MAT-
LAB 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 func-
tion, you will see it puts the
output variable in brackets.
Brackets are required if you
want more than one out-
put 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 t Back in Chapter 2: Scripts,
the output variable whatever you want, but as a convention, I like to call it res. I mentioned that I always
find that students like to
Usually the last thing a function does is assign a value to the output variable.
use the “Run” button to
Once you have defined a new function, you call it the same way you call run their scripts. However,
built-in MATLAB functions. If you call the function as a statement, MATLAB puts when you have a function
the result into ans: with input variables, you
can not simply click the
>> myfunc(1) Run button. Doing so is
s = 0.84147 equivalent to typing the
name of the M-file in the
c = 0.54030
Command Window . The func-
res = 1.3818
tion requires an input, so
ans = 1.3818 this will not work. You need
to click on the drop-down
But it is more common (and better style) to assign the result to a variable: arrow and select “Run: type
code to run”. This is demon-
>> y=myfunc(1) strated in the screen cast
available here. But at this
s = 0.84147
point, why not just get in
c = 0.54030
the habit of running your
res = 1.3818 scripts and functions from
y = 1.3818 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 While you are debugging a new function, you might want to display interme-
the name of the M-file and diate results like this, but once it is working, you will want to add semi-colons
the name of the function. to make it a silent function. Most built-in functions are silent; they compute a
We will discuss my reason result, but they don’t display anything (except sometimes warning messages).
for this momentarily.

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 ex-
plains what the function does.
Listing 5.3 myfunc_comment.m t When you use the New
button to create a new func-
1 % res = myfunc_comment(x) tion, you will see it puts the
2 % compute the Manhattan distance from the origin to the
documentation material
3 % point on the unit circle with angle (x) in radians.
after the function signature.
4
5 function res = myfunc_comment(x) Both formats are correct
6 s = sin(x); and have the same effect
7 c = cos(x); (give it a try). I prefer to put
8 res = abs(s) + abs(c); the comments/documen-
9 end tation at the top of a file,
the same convention we
used when writing scripts.
When you ask for help, MATLAB prints the comment you provide. This is also why I use the
New Script button to create
>> help myfunc_comment function files. Personally,
res = myfunc_comment(x) the least I need to try and
Compute the Manhattan distance from the origin to the remember the better.
point on the unit circle with angle (x) in radians.

There are lots of conventions about what should be included in these com-
ments. 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,
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 mis-


take made by beginners
and can make debugging 5.4 Function names
tricky. Remember the first
step of Incremental Devel- There are three “gotchas” that come up when you start naming functions. The
opment. Start by creating a first is that the “real” name of your function is determined by the file name, not by
function that takes no input the name you put in the function signature. As a matter of style, you should make
and performs simple task, sure that they are always the same, but if you make a mistake, or if you change
such as res =5. Then call the name of a function, it is easy to get confused.
the function from the com-
In the spirit of making errors on purpose, create a copy myfunc_silent.m and
mand line. This way you
Save As myfunc_name.m. In myfunc_name.m, change the name of the function
make sure the function you
think you are calling is what to something_else, and then run it again.
is actually being called. 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 Ed- >> y = myfunc_name(1)
itor and start by writing
y = 1.3818
the function signature,
MATLAB will suggest the
name of the function as the >> y = something_else(1)
name of the file. It will help Undefined command/function 'something_else'.
you try to avoid the “first
gotcha.” The second gotcha is that the name of the file can’t have spaces. We encoun-
tered the same rule when naming variables, where we used underscores in place
t In MATLAB 2015a, MAT- of spaces when desired. More generally, valid function names follow the same
LAB will display a warning rules as variable names. For example, if you write a function and name the file my
message in a new pop-up func.m, which the MATLAB editor will happily allow you to do, and then try to
window if you attempt the run it, you get:
“second gotcha,” and will
suggest a change to avoid it.
You can fix the name right
>> y = my func(1)
there in the pop-up window. Undefined function or variable 'my'.
(To be clear, I am referring
to when you try to save the
file in the Editor, not when
you try to run it.)
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 cre-
ated 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 t Before we move on to Ex-
variables exist too! So take a minute when you name your function. For more amples, recall from our
discussion in Chapter 4 that
insight on the third gotcha, have a look at the MATLAB documentation pages
MATLAB’s built-in func-
“Files and Folders that MATLAB Accesses and “Function Precedence Order.” tions operate element-wise
on vectors. The same is
Example 5.1 true with functions that
you write, so long as you
MATLAB contains a function sqrt to compute the square root of a number. Write use element-wise opera-
a function named cubert to compute the cube root of a number. tions (i.e., .*). Remember,
a scalar is a 1 by 1 matrix,
Solution: (Link to screen cast and accompanying M-file.) The cube root of x is so element-wise operators
x 1/3 . Let’s write the function. work as normal with scalars.
Listing 5.6 cubert.m So writing your functions
in this way is not a problem.
1 % res = cubert (n) However, in this Chapter we
2 % Computing the cube root of a number n. will only consider functions
3 function res = cubert (n)
that pass and return scalars.
4 res = n^(1/3);
Functions of vectors will be
5 end
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 func-
tion, 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 vari-
able 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 guar-
anteed 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 unneces-


sary, 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 % res = deg_to_rad(n)
2 % Converting an angle from degrees to radians
3 function res = deg_to_rad(n)
4 res = n*pi/180;
5 end

Listing 5.8 sin_deg.m


1 % res = sin_deg(n)
2 % Computing the sin of angle n in units of degress
3 function res = sin_deg(n)
4 x = deg_to_rad(n);
5 res = sin(x);
6 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 % res = hypotenuse(a, b)
2 % compute the hypotenuse of a right triangle with sides of
3 % length a and b.
4 function res = hypotenuse(a, b)
5 res = sqrt(a^2 + b^2);
6 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 % res = rect_area (l,h)
2 % Computing the area of a rectangle of length l and heigh h.
3 function res = rect_area(l,h)
4 res = l*h;
5 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 radi-
ans 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 % res = sin_angle (x,units)
2 % Computing the sine of angle x. The units of x are determined by
3 % the variable units.
4 % if units == 1, we are using radians
5 % if units == -1, we are using degrees
6 % else you made a mistake!
7 function res = sin_angle (x,units)
8 if units == 1
9 res = sin(x);
10 elseif units == -1
11 res = sin_deg(x); % Could instead use built-in sind(x)
12 else
13 disp('Incorrect choice of units. Please try again.')
14 res = NaN;
15 end
16 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 inte-
ger.

>> 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 % res = isintegral(x)
2 % checks whether or not x is an integer. If it is, it returns
3 % a value of 1 for true. If not, it returns a value of 0 for false.
4 function res = isintegral(x)
5 if round(x) == x
6 res = 1;
7 else
8 res = 0;
9 end
10 end

This function is good enough for most applications, but remember that floating-
point 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 % res = iseven(n)
2 % A function to determine if integer n is even. An even number is a
3 % number that when divided by 2 has a remainder of 0. We will compute
4 % the remainder of dividing n by 2 using the MATLAB function mod. If
5 % mod(n,2) is found to be 0, we have an even number so set
6 % res = 1 (true). Otherwise, set res = 0 (false).
7 function res = iseven(n)
8 r = mod(n,2);
9 if r == 0
10 res = 1;
11 else
12 res = 0;
13 end
14 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 % res = iseven_2(n)
2 % A function to determine if integer n is even. An even number is a
3 % number that when divided by 2 has a remainder of 0. We will take the
4 % integer, divide by two, and then round the answer to the nearest integer.
5 % We will then if the rounded and original answer are equivalent. If they
6 % are, the we have a remainder of zero and we have an evn number, so set
7 % res = 1 (true). Otherwise, set res = 0 (false).
8 function res = iseven_2(n)
9 r1 = n/2;
10 r2 = round(r1);
11 if r1==r2
12 res = 1;
13 else
14 res = 0;
15 end
16 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 149

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:
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 = 1
b = 2
b = 3
b = 4

a = 2 b = 1
b = 2
b = 3
b = 4

a = 3 b = 1
b = 2
b = 3
b = 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 1.0000 1.4142


ans = 1.0000 2.0000 2.2361
5.9 Conditions and flags 151

ans = 1.0000 3.0000 3.1623


ans = 1.0000 4.0000 4.1231
ans = 2.0000 1.0000 2.2361
ans = 2.0000 2.0000 2.8284
ans = 2.0000 3.0000 3.6056
ans = 2.0000 4.0000 4.4721
ans = 3.0000 1.0000 3.1623
ans = 3.0000 2.0000 3.6056
ans = 3.0000 3.0000 4.2426
ans = 3 4 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 1.0000 1.4142
ans = 1.0000 2.0000 2.2361
ans = 1.0000 3.0000 3.1623
ans = 1.0000 4.0000 4.1231
ans = 2.0000 2.0000 2.8284
ans = 2.0000 3.0000 3.6056
ans = 2.0000 4.0000 4.4721
ans = 3.0000 3.0000 4.2426
ans = 3 4 5

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 0
ans = 2.2361 0
ans = 3.1623 0
ans = 4.1231 0
ans = 2.8284 0
ans = 3.6056 0
ans = 4.4721 0
ans = 4.2426 0
ans = 5 1

I chose the ranges for a and b to be small (so the amount of output is man-
ageable), 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 153

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 function res = find_triples()


2 for a=1:3
3 for b=a:4
4 c = hypotenuse(a, b);
5 flag = isintegral(c);
6 if flag
7 [a, b, c]
8 end
9 end
10 end
11 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.
154 Chapter 5 Functions

1 function res = find_triples(n)


2 for a=1:n
3 for b=a:n
4 c = hypotenuse(a, b);
5 flag = isintegral(c);
6 if flag
7 [a, b, c]
8 end
9 end
10 end
11 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 function res = hypotenuse(a, b, d)


2 res = (a.^d + b.^d) ^ (1/d);
3 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 155

>> 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:
156 Chapter 5 Functions

Listing 5.15 find_triples.m


1 % res = find_triples (n)
2 % finding Pythagorean triples over the range 1 to n.
3 function res = find_triples (n)
4 for a=1:n
5 for b=a:n
6 if gcd(a,b) > 1
7 continue
8 end
9 c = hypotenuse(a, b);
10 if isintegral(c)
11 [a, b, c]
12 end
13 end
14 end
15 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 % res = fib_triple(n)
2 % checks whether the provided formula using 3 consecutive Fibonacci
3 % numbers produces a Pythagorean triple over the range 1 to n.
4 % The result will be a vector containing 1 (for true) and 0 (for false)
5 % for an n corresponding to the index.
6 function res = fib_triple(n)
7 F = fibonacci_seq(n+3);
8 for i = 1:n
9 a = F(i) * F(i+3);
10 b = 2 * F(i+1) * F(i+2);
11 c = F(i+1)^2 + F(i+2)^2;
12 % Will be 1 if true (it is a triple), 0 if not
13 res(i) = istriple(a, b, c);
14 end
15 end

Listing 5.17 fibonacci_seq.m


1 % res = fibonacci_seq(n)
2 % computing the fibonacci series out to n
3 function res = fibonacci_seq(n)
4 F(1) = 1;
5 F(2) = 1;
6 for i=3:n
7 F(i) = F(i-1) + F(i-2);
8 end
9 res = F;
10 end
158 Chapter 5 Functions

Listing 5.18 istriple.m


1 % res = istriple a, b, c)
2 % Checking if a, b, and c form a Pythagorean triple.
3 % If yes, then return 1 for true. If fale, return 0.
4 function res = istriple(a, b, c)
5 % We need to be careful using ==. If we find that
6 % it is not working as expected here, we can modify.
7 if hypotenuse(a, b) == c
8 res = 1;
9 else
10 res = 0;
11 end
12 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 execu-
tion 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 % res = isodd(n)
2 % A function to determine if integer n is odd. An odd number is a
3 % number that when divided by 2 the remainder is 1. We will compute the
4 % remainder of dividing n by 2 using the MATLAB function mod. If
5 % mod(n,2) is found to be 1, we have an odd number so set res = 1 (true).
6 % Otherwise, set res = 0 (false).
7 %
8 function res = isodd(n)
9 r = mod(n,2);
10 if r == 1
11 res = 1;
12 else
13 res = 0;
14 end
15 end
160 Chapter 5 Functions

Listing 5.20 one_odd.m


1 % res = one_odd(n,m)
2 % The function multiplies n*m if and only if only only one number is
3 % odd. Otherwise, it adds them together. This function uses the
4 % function isodd, so it must be in the same folder or in your path.
5 %
6 function res = one_odd(n,m)
7 % Rather than call isodd a bunch of times, call it once
8 % and store the result to a variable
9 flag_n = isodd(n);
10 flag_m = isodd(m);
11 %
12 % If both n and m are odd, add the numbers together
13 % Note that since true is equal ts 1, we can leave out the
14 % == 1
15 if flag_n && flag_m
16 res = n+m;
17 % If both n and m are not odd, they are both even,
18 % add the numbers together
19 elseif flag_n == 0 && flag_m==0
20 res = n+m;
21 % If both numbers are not odd and both numbers are not even, then
22 % it must be that one number is even and one number is odd. For
23 % this case multiply the numbers together
24 else
25 res = n*m;
26 end % end if
27 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 tem-
peratures for which to perform calculations.

Listing 5.21 antoine_p.m


1 % res = antoine_p( a,b,c,t_C )
2 %
3 % Computing the vapor prssure of a fluid using Antoine's equation. The user
4 % must provide Antoine parameters A, B, and C. I will assume they are from
5 % the same source as the ethanol parameters we have used previously, so the
6 % default units will be t in degress C and p in mmHg. Please be sure to
7 % check this for future cases. The user must also provide the temperature
8 % in degrees C (t_C). The result will be pressure in mmHg.
9 %
10 % a,b,c = Antoine parameters with t in C and p in mmHg
11 % t_C = temperature in Celsius
12 % res = vapor pressure in mmHg
13 %
14 function res = antoine_p( a,b,c,t_C )
15
16 log_p_mmHg = a-b./(t_C+c);
17 res = 10.^(log_p_mmHg);
18
19 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 % res = antoine_p_units( a,b,c,t_C,units )
2 %
3 % Computing the vapor prssure of a fluid using Antoine's equation. The user
4 % must provide Antoine parameters A, B, and C. I will assume they are from
5 % the same source as the ethanol parameters used previously, so the
6 % default units will be t in degress C and p in mmHg. Please be sure to
7 % check this for future cases. The user must also provide the temperature
8 % in degrees C (t_C). This will result in a pressure in mmHg.
9 % In this version of the code, the user must also provide a value for
10 % units, which will indicate the desired units for the final reported
11 % pressure. The value of units is as follows:
12 %
13 % a,b,c = Antoine parameters with t in C and p in mmHg
14 % t_C = temperature in Celsius
15 % units = 1 for mmHg
16 % = 2 for atm
17 % = 3 for kPa
18 % = 4 for bar
19 %
20 function res = antoine_p_units( a,b,c,t_C,units )
21 % First, call antoine_p to compute p in mmHg
22 p_mmHg = antoine_p( a,b,c,t_C );
23 % Checking for desired units
24 if units == 1 % mmHg
25 res = p_mmHg;
26 elseif units == 2 % atm
27 res = p_mmHg/760;
28 elseif units == 3 % kPa
29 res = p_mmHg*101.325/760;
30 elseif units == 4 % bar
31 res = p_mmHg*101.325/760/100;
32 else
33 disp('Invalid choice of units. p is returned in mmHg.')
34 res = p_mmHg;
35 end % if statement
36 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 per-
form 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 % res = antoine_clap_plot( a,b,c,t_C_min,t_C_max )
2 %
3 % Generating a Clapeyron plot of a fluid using Antoine's equation. The user
4 % must provide Antoine parameters A, B, and C. I will assume they are from
5 % the same source as the ethanol parameters used previously, so the
6 % default units will be t in degress C and p in mmHg. Pleasue be sure to
7 % check this for future cases. The user must also provide the
8 % miniumum and maximum temperature in degrees C (t_C_min and t_C_max).
9 %
10 % I will then generate a Clapeyron plot over the range, or a plot of
11 % log10 psat vs 1/T.
12 % Note that here I will convert t to K.
13 %
14 % Also, note that I will not assign anything to res, so nothing will
15 % be returned from the function to my Workspace. Only the plot will
16 % be generated.
17 %
18 % a,b,c = Antoine parameters with t in C and p in mmHg
19 % t_C_min and t_C_max = minimum and maximum temperature in Celsius
20 % res = Nothing is returned
21 %
22 function res = antoine_clap_plot( a,b,c,t_C_min,t_C_max )
23 % Creating a vector of temperatures in C from tmin to tmax in
24 % increments of 1.
25 t_C = t_C_min:1:t_C_max;
26 % Computing log10 psat with psat in mmHg for each temperature.
27 % Here I will use elementwise operations.
28 log_p_mmHg =a-b./(t_C+c);
29 % Computing 1/t with t in K
30 tinv = 1./(t_C+273.15);
31 % Plotting
32 plot(log_p_mmHg,tinv)
33
34 % labeling the axis
35 xlabel('1/T [1/K]')
36 ylabel('log10 p/mmHg')
37
38 % plot title
39 title('Clapeyron plot')
40 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 % function res = mb_speed( mw, t_C )
2 %
3 % Function to compute the average molecular speed of a
4 % species using the Maxwell-Boltzmann equation. The
5 % user must provide the molecular weight of the species
6 % in g/mol (or equivalently Da or amu), and the desired
7 % temperature in oC. The result will be the speed in
8 % m/s.
9 %
10 % mw = molecular weight in g/mol
11 % t_C = temperature in Celsius
12 % res = speed in m/s
13 %
14
15 function res = mb_speed( mw, t_C )
16 % Convert the temperature from oC to K
17 t_K = t_C+273.15;
18 % Avogadro's number
19 navo = 6.022e23;
20 % Gas constant in J/mol K
21 r = 8.314;
22 % Boltzmann's constant, kB, in J/K
23 kB = r/navo;
24 % Calculating the mass of a molecule in kg
25 mass_kg = mw/navo/1000;
26 % Calculating the speed in m/s
27 res = sqrt(3*kB*t_K/mass_kg)
28 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 super-
heated 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 equa-
tion of state. Remember pV ig = nRT so that

V ig RT
v ig = = (5.1)
n p

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 compress-
ibility factor. For a real fluid then we have

RT
v=Z = Z v ig (5.2)
p

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

Bp
Z ≈ 1+ (5.3)
RT
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) (5.4)
RTc

Therefore
RTc £ (0)
B + ωB (1)
¤
B= (5.5)
pc
where
0.422
B (0) = 0.083 − (5.6)
Tr1.6
1 http://webbook.nist.gov/chemistry/fluid/
166 Chapter 5 Functions

0.172
B (1) = 0.139 − (5.7)
Tr4.2
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 % res = v_ideal_gas(t,p_bar)
2 %
3 % Calculating the molar volume of a fluid in units of cm^3/mol using
4 % the ideal gas equation of state. The user must specify the
5 % temperature (t) in units of K, and the pressure (p) in units of bar.
6 % The function will then calculate the molar volume in units of cm^3/mol.
7 %
8 % t = temperature in K
9 % p_bar = pressure in bar
10 % res = molar volume in cc/mol
11 %
12 function res = v_ideal_gas( t,p_bar )
13 % Converting the pressure from bar to Pa so that everything
14 % is in SI units
15 p = p_bar*1e5;
16 % The molar gas constant in units of J/(mol K), SI units
17 r = 8.314;
18 % With all SI units, computing the molar volume in m^3/mol
19 v = r*t/p;
20 % Converting the molar volume to cm^3/mol
21 res = v*(100^(3));
22 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 % res = v_virial(t,p_bar,tc,pc_bar,omega)
2 %
3 % Calculating the molar volume of a fluid in units of cm^3/mol using the
4 % truncated virial equation with correspondings states to estimate
5 % the second virial coefficient, B.
6 % The user must specify the temperature (t) and critical temperature(tc)
7 % in units of K, the pressure (p) and critical pressure (pc) in units
8 % of bar, and the accentric factor (which is dimensionless).
9 % The function will then calculate the molar volume in units
10 % of cm^3/mol.
11 %
12 % t,tc = temperature and critical temperature in K
13 % p_bar,pc_bar = pressure and critical pressure in bar
14 % omega = accentric factor (dimensionless)
15 % res = molar volume in cc/mol
16 %
17 % Note that we will use the function v_ideal_gas to compute the
18 % molar volume of an ideal gas, which must be in the current path.
19 %
20 function res = v_virial( t,p_bar,tc,pc_bar,omega )
21 % First, we will want to work in SI units, so let's convert p_bar and
22 % pc_bar to Pa before we forget
23 p = p_bar*1e5;
24 pc = pc_bar*1e5;
25 % Molar gas constant in J/(mol K)
26 r = 8.314;
27 % Calculating the relative temperature, tr. It is important here that
28 % we are using absolute temperature units.
29 tr = t/tc;
30 %
31 % Now let's calculate B0 and B1 using the provided equations
32 b0 = 0.083-0.422/(tr^(1.6));
33 b1 = 0.139-0.172/(tr^(4.2));
34 % Now calculating B
35 b = r*tc/pc*(b0+omega*b1);
36 %
37 % Calculating the compressibility factor Z
38 z = 1 + b*p/(r*t);
39 %
40 % Calculating the ideal gas molar volume using our function
41 % v_ideal_gas which takes as input T in K and p in bar.
42 % It returns the molar volume in units of cm^3/mol.
43 vig = v_ideal_gas(t,p_bar);
44 %
168 Chapter 5 Functions

45 % Finally, calculating the molar volume of our fluid in cm^3/mol using


46 % the truncated virial. Note that since Z is dimensionless, multiplying
47 % by the ideal gas volume in cm^3/mol gives the desired result.
48 res = z*vig;
49 end

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 devia-
tions 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 169

5.16 Glossary
side-effect: An effect, like modifying the workspace, that is not the primary pur-
pose 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.
170 Chapter 5 Functions

5.17 Exercises
Exercise 5.1 “Yaws’ Critical Property Data for Chemical Engineers and Chemists”2 rec-
ommends use of a modified Watson equation to calculate the enthalpy of vaporization as
a function of temperature:

T n
µ ¶
vap
∆H = A 1− (5.8)
B
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 A B n Tmin [K ] Tmax [K ]


ethanol 60.8036 516.25 0.380 300.00 516.25
ethane 21.3420 305.42 0.403 90.35 305.42
propane 26.8896 369.82 0.365 85.44 369.82
n-butane 33.0198 425.18 0.377 134.86 425.18
n-pentane 39.8543 469.65 0.398 143.42 469.65
n-hexane 45.61 507.43 0.401 177.84 507.43
n-heptane 49.73 540.26 0.386 182.56 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:

64 λ1 λ2
f = + ´+ (5.9)
τ
1 + exp τ2 −Re ·
³ ³ ´
Re 1 + exp 1 −Re ²
100 600 D

where f is the friction factor, Re is the Reynold’s number, defined as:

ρV D
Re =
µ
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 ¯
λ2 = ¯λ1 −
¯ ¯
1 ²
¡ ¢ ¯
¯ −2 log10 3.7065 · D
¯

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 corre-
sponding 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 % res = v_virial(t,p_bar,tc,pc_bar,omega)
2 %
3 % Calculating the molar volume of a fluid in units of cm^3/mol using the
4 % truncated virial equation with correspondings states to estimate
5 % B. The user must specify the temperature (t) and critical temperature(tc)
6 % in units of K, the pressure (p) and critical pressure (pc) in units
7 % of bar, and the accentric factor (which is dimensionless).
8 % The function will then calculate the molar volume in units
9 % of cm^3/mol.
10 %
11 function res = v_virial( t,p_bar,tc,pc_bar,omega )
12 % First, we will want to work in SI units, so let's convert p_bar and
13 % pc_bar to Pa before we forget
14 p = p_bar*1e5;
15 pc = pc_bar*1e5;
16 % Molar gas constant in J/(mol K)
17 r = 8.314;
18 % Calculating the relative temperature, tr. It is important here that we
19 % are using absolute temperature units.
20 tr = t/tc;
21 %
22 % Now let's calculate B0 and B1 using the provided equations
23 b0 = 0.083-0.422/(tr^(1.6));
24 b1 = 0.139-0.172/(tr^(4.2));
25 % Now calculating B
26 b = r*tc/pc*(b0+omega*b1);
27 %
28 % Calculating the compressibility factor Z
29 z = 1 + b*p/(r*t);
30 %
31 % Calculating the ideal gas molar volume. We will use our function to do
32 % this which takes as input T in K and p in bar. It returns the molar
33 % volume in units of cm^3/mol, which is what we desire.
34 vig = v_ideal_gas(t,p_bar);
35 %
36 % Finally, calculating the molar volume of our fluiding in cm^3/mol using
37 % the truncated virial. Note that since Z is dimensionless, multiplying by
38 % the ideal gas volume in cm^3/mol gives the desired result.
39 res = z*vig;
40 end
41
42 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43
44 % res = v_ideal_gas(t,p)
45 %
46 % Calculating the molar volume of a fluid in units of cm^3/mol using
47 % the ideal gas equation of state. The functiona call must pass
48 % the temperature (t) in units of K, and the pressure (p) in
49 % units of bar. The function will then calculate the molar volume in units
50 % of cm^3/mol.
51 %
52 function res = v_ideal_gas( t,p_bar )
53 % Converting the pressure from bar to Pa so that everything is in SI units
6.1 Functions and files 175

54 p = p_bar*1e5;
55 % The molar gas constant in units of J/(mol K), SI units
56 r = 8.314;
57 % With all SI units, computing the molar volume in m^3/mol
58 v = r*t/p;
59 % Converting the molar volume to cm^3/mol
60 res = v*(100^(3));
61 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 top-
level 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 function res = v_virial()


2 test_pass = 10
3 test_return = v_ideal_gas(test_pass)
4 end
5
6 function res = v_ideal_gas(r)
7 test_in = r
8 res = r;
9 end

Executing from the Command Window


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 function res = v_virial ()


2 test = v_ideal_gas(440,60)
3 end
4
5 function res = v_ideal_gas(t,p_bar)
6 p = p_bar*1e5;
7 r = 8.314;
8 v = r*t/p;
9 res = v*(100^(3));
10 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 177

1 function res = v_virial()


2 % parameters
3 t = 440;
4 p_bar = 60;
5 tc = 425.15;
6 pc_bar = 37.960;
7 omega = 0.201;
8 %
9 p = p_bar*1e5;
10 pc = pc_bar*1e5;
11 r = 8.314;
12 tr = t/tc;
13 b0 = 0.083-0.422/(tr^(1.6));
14 b1 = 0.139-0.172/(tr^(4.2));
15 b = r*tc/pc*(b0+omega*b1);
16 z = 1 + b*p/(r*t);
17 vig = v_ideal_gas(t,p_bar);
18 res = z*vig;
19 end
20
21 function res = v_ideal_gas(t,p_bar)
22 p = p_bar*1e5;
23 r = 8.314;
24 v = r*t/p;
25 res = v*(100^(3));
26 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)
178 Chapter 6 Functions of vectors

example:

1 function res = display_vector(X)


2 X
3 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 function res = mysum(X)


2 total = 0;
3 for i=1:length(X)
4 total = total + X(i);
5 end
6 res = total;
7 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 179

Listing 6.3 myapply.m

1 function res = myapply(X)


2 % Pre-size Y, the vector containing the result
3 Y = zeros(1,length(X));
4 for i=1:length(X)
5 Y(i) = X(i)^2;
6 end
7 res = Y;
8 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 % function res = find_negative_element(X)
2 %
3 % Function to find the index of the first negative
4 % number in vector X. An answer will only be
5 % returned to the Command Window if a negative
6 % number is found. This is an improvement over
7 % the previous version of our code.
8
9 function res = find_negative_element(X)
10 for i=1:length(X)
11 if X(i) < 0
12 res = i;
13 break
14 end
15 end
16 end

Note that if you desired, if there is no negative element, you could have MATLAB
throw an error message with the command, error.
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 function [ output_args ] = untitled( input_args )


2 %UNTITLED Summary of this function goes here
3 % 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 181

Listing 6.6 v_virial_1.m


1 function [res] = v_virial_1( t,p_bar,tc,pc_bar,omega )
2 % First, we will want to work in SI units, so let's convert p_bar and
3 % pc_bar to Pa before we forget
4 p = p_bar*1e5;
5 pc = pc_bar*1e5;
6 % Molar gas constant in J/(mol K)
7 r = 8.314;
8 % Calculating the relative temperature, tr. It is important here that we
9 % are using absolute temperature units.
10 tr = t/tc;
11 %
12 % Now let's calculate B0 and B1 using the provided equations
13 b0 = 0.083-0.422/(tr^(1.6));
14 b1 = 0.139-0.172/(tr^(4.2));
15 % Now calculating B
16 b = r*tc/pc*(b0+omega*b1);
17 %
18 % Calculating the compressibility factor Z
19 z = 1 + b*p/(r*t);
20 %
21 % Calculating the ideal gas molar volume. We will use our function to do
22 % this which takes as input T in K and p in bar. It returns the molar
23 % volume in units of cm^3/mol, which is what we desire.
24 vig = v_ideal_gas(t,p_bar);
25 %
26 % Finally, calculating the molar volume of our fluiding in cm^3/mol using
27 % the truncated virial. Note that since Z is dimensionless, multiplying by
28 % the ideal gas volume in cm^3/mol gives the desired result.
29 res = z*vig;
30 end
31
32 function res = v_ideal_gas( t,p_bar )
33 % Converting the pressure from bar to Pa so that everything is in SI units
34 p = p_bar*1e5;
35 % The molar gas constant in units of J/(mol K), SI units
36 r = 8.314;
37 % With all SI units, computing the molar volume in m^3/mol
38 v = r*t/p;
39 % Converting the molar volume to cm^3/mol
40 res = v*(100^(3));
41 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, separat-
ing the variables by a comma.
182 Chapter 6 Functions of vectors

Listing 6.7 v_virial_2.m


1 function [res,z,vig] = v_virial_2( t,p_bar,tc,pc_bar,omega )
2 % First, we will want to work in SI units, so let's convert p_bar and
3 % pc_bar to Pa before we forget
4 p = p_bar*1e5;
5 pc = pc_bar*1e5;
6 % Molar gas constant in J/(mol K)
7 r = 8.314;
8 % Calculating the relative temperature, tr. It is important here that we
9 % are using absolute temperature units.
10 tr = t/tc;
11 %
12 % Now let's calculate B0 and B1 using the provided equations
13 b0 = 0.083-0.422/(tr^(1.6));
14 b1 = 0.139-0.172/(tr^(4.2));
15 % Now calculating B
16 b = r*tc/pc*(b0+omega*b1);
17 %
18 % Calculating the compressibility factor Z
19 z = 1 + b*p/(r*t);
20 %
21 % Calculating the ideal gas molar volume. We will use our function to do
22 % this which takes as input T in K and p in bar. It returns the molar
23 % volume in units of cm^3/mol, which is what we desire.
24 vig = v_ideal_gas(t,p_bar);
25 %
26 % Finally, calculating the molar volume of our fluiding in cm^3/mol using
27 % the truncated virial. Note that since Z is dimensionless, multiplying by
28 % the ideal gas volume in cm^3/mol gives the desired result.
29 res = z*vig;
30 end
31
32 function res = v_ideal_gas( t,p_bar )
33 % Converting the pressure from bar to Pa so that everything is in SI units
34 p = p_bar*1e5;
35 % The molar gas constant in units of J/(mol K), SI units
36 r = 8.314;
37 % With all SI units, computing the molar volume in m^3/mol
38 v = r*t/p;
39 % Converting the molar volume to cm^3/mol
40 res = v*(100^(3));
41 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 183

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
184 Chapter 6 Functions of vectors

function will probably work on vectors.


For example, here is the very first function we wrote:

1 function res = myfunc (x)


2 s = sin(x)
3 c = cos(x)
4 res = abs(s) + abs(c)
5 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 Sec-
tion 5.5 (and Listing 5.9):

1 function res = hypotenuse(a, b)


2 res = sqrt(a^2 + b^2);
3 end

This doesn’t work on vectors because the ^ operator tries to do matrix exponenti-
ation, 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 function res = hypotenuse(a, b)


2 res = sqrt(a.^2 + b.^2);
3 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:

i
X
Ci = 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 cumu-
lative 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 % function res = cumulative_sum(X)
2 %
3 % Function to compute the cumulative sum of vector X which
4 % is returned as res.
5 %
6 function res = cumulative_sum(X)
7 % Y will be our vector to store the cumulative sum.
8 % Initialize it with values of zero since we will be
9 % performing a summation
10 Y = zeros(1,length(X));
11
12 for i=1:length(X)
13 for j=1:i
14 Y(i) = Y(i) + X(j);
15 end
16 end
17
18 res = Y;
19 end

>> V = 1:5
V = 1 2 3 4 5

>> C = cumulative_sum(V)
C = 1 3 6 10 15

This agrees exactly with cumsum.

The inverse operation of cumsum is diff, which computes the difference be-
tween 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 4

But it isn’t.
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 % function res = mydiff(X)
2 %
3 % Function to compute the inverse of the cumulative sum,
4 % which is returned as res.
5 %
6 function res = mydiff(X)
7 % Y will be our vector to store the inverse cumulative sum.
8 % Initialize it with values of X.
9 Y = X;
10
11 for i=2:length(X)
12 Y(i) = Y(i)-X(i-1);
13 end
14
15 res = Y;
16
17 end

>> V = 1:5
V = 1 2 3 4 5

>> C = cumulative_sum(V)
C = 1 3 6 10 15

>> VI = mydiff(C)
VI = 1 2 3 4 5

6.7 Products and ratios


The multiplicative version of cumsum is cumprod, which computes the cumulative
product. In math notation, that’s:

i
Y
Pi = Vj
j =1

In MATLAB, that looks like:


>> V = 1:5
V = 1 2 3 4 5
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 % function res = cumulative_product(X)
2 %
3 % Function to compute the cumulative product of vector X which
4 % is returned as res.
5 %
6 function res = cumulative_product(X)
7 % Y will be our vector to store the cumulative product.
8 % Initialize it with values of one since we will be
9 % performing a continuous product.
10 Y = ones(1,length(X));
11
12 for i=1:length(X)
13 for j=1:i
14 Y(i) = Y(i)*X(j);
15 end
16 end
17
18 res = Y;
19
20 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 canp 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 % function res = myratio(X)
2 %
3 % Function to compute the inverse of the continuous product,
4 % which is returned as res.
5 %
6 function res = myratio(X)
7 % Y will be our vector to store the inverse continous product.
8 % Initialize it with values of X.
9 Y = X;
10
11 for i=2:length(X)
12 Y(i) = Y(i)/X(i-1);
13 end
14
15 res = Y;
16
17 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 % function res = myratio_clever(X)
2 %
3 % Function to compute the inverse of the continuous product,
4 % which is returned as res.
5 %
6 function res = myratio_clever(X)
7 Y = log(X);
8 Z = mydiff(Y);
9 res = exp(Z);
10 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 function res = exists(X)


2 for i=1:length(X)
3 if X(i) > 0
4 res = 1;
5 return
6 end
7 end
8 res = 0;
9 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 function res = exists_2(X)


2 res = 0;
3 for i=1:length(X)
4 if X(i) > 0
5 res = 1;
6 break
7 end
8 end
9 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 ele-
ments 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 % function res = forall(X)
2 %
3 % A function to check if all of the elements
4 % of X are positive. We know that if at least
5 % one of the elements is negative, then they
6 % are not all positive. So we check this
7 % simpler case.
8 %
9 % Return 1 for true (all positive), 0 for false
10 %
11 function res = forall(X)
12 res = 1;
13 for i = 1:length(X)
14 if X(i) < 0
15 res = 0;
16 return
17 end
18 end
19 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 193

>> L = V>0
L = 0 0 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 corre-
sponds 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 ab-
stractly as “find the indices of elements of V that are greater than 10.”
We can also use find to write exists more concisely:

1 function res = exists(X)


2 L = find(X>0)
3 res = length(L) > 0
4 end

This could also be achieved using a logical vector:

1 function res = exists(X)


2 res = 1; % Start by assuming true
3 if sum(X>0) == 0 % Check if every element is negative
4 res = 0;
5 end
6 end
194 Chapter 6 Functions of vectors

And here is an even more compact solution:

1 function res = exists(X)


2 res = sum(X>0) ~= 0;
3 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 % function res = forall_find(X)
2 %
3 % A function to check if all of the elements
4 % of X are positive. We know that if at least
5 % one of the elements is negative, then they
6 % are not all positive. So we check this
7 % simpler case.
8 %
9 % Return 1 for true (all positive), 0 for false
10 %
11 function res = forall_find(X)
12 L = find(X<0);
13 res = length(L) == 0;
14 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 docu-
mentation, here is what the updated function would look like:
6.10 Logical vectors 195

Listing 6.18 forall_find_2.m


1 % function res = forall_find_2(X)
2 %
3 % A function to check if all of the elements
4 % of X are positive. We know that if at least
5 % one of the elements is negative, then they
6 % are not all positive. So we check this
7 % simpler case.
8 %
9 % Return 1 for true (all positive), 0 for false
10 %
11 function res = forall_find_2(X)
12 L = find(X<0);
13 res = isempty(L);
14 end

And in the interest of exploring alternatives, we could use a logical vector instead
of find.

Listing 6.19 forall_logical.m


1 % function res = forall_logical(X)
2 %
3 % A function to check if all of the elements
4 % of X are positive. We know that if at least
5 % one of the elements is negative, then they
6 % are not all positive. So we check this
7 % simpler case.
8 %
9 % Return 1 for true (all positive), 0 for false
10 %
11 function res = forall_logical(X)
12 L = X<0;
13 res = sum(L)==0;
14 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 % function res = forall_logical_any(X)
2 %
3 % A function to check if all of the elements
4 % of X are positive. We know that if at least
5 % one of the elements is negative, then they
6 % are not all positive. So we check this
7 % simpler case.
8 %
9 % Return 1 for true (all positive), 0 for false
10 %
11 function res = forall_logical_any(X)
12 L = X<0;
13 res = ~any(L);
14 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 func-
tion. 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 % res = antoine_p_units( a,b,c,tC,units )
2 %
3 % Computing the vapor prssure of a fluid using Antoine's equation. The user
4 % must provide Antoine parameters A, B, and C. I will assume they are from
5 % the same source as the ethanol parameters provided in class, so the
6 % default units will be t in degress C and p in mmHg. Pleasue be sure to
7 % check this for future cases. The user must also provide the temperature
8 % in degrees C (tC). This will result in a pressure in mmHg.
9 % In this version of the code, the user must also provide a value for
10 % units, which will indicate the desired units for the final reported
11 % pressure. The value of units is as follows:
12 % units = 1 for mmHg
13 % = 2 for atm
14 % = 3 for kPa
15 % = 4 for bar
16 %
17 function res = antoine_p_units( a,b,c,tC,units )
18 % First, call antoine_p to compute p in mmHg
19 logp_mmHg = a-b./(tC+c);
20 p_mmHg = 10.^(logp_mmHg);
21 % Checking for desired units
22 if units == 1 % mmHg
23 res = p_mmHg;
24 elseif units == 2 % atm
25 res = p_mmHg./760;
26 elseif units == 3 % kPa
27 res = p_mmHg.*101.325./760;
28 elseif units == 4 % bar
29 res = p_mmHg.*101.325./760./100;
30 else
31 disp('Invalid choice of units. p is returned in mmHg')
32 res = p_mmHg;
33 end
34 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 199

tC =
Columns 1 through 8
-114.1000 -84.1000 -54.1000 -24.1000 5.9000 35.9000 65.9000 95.9000
Columns 9 through 12
125.9000 155.9000 185.9000 215.9000

>> p_bar = antoine_p_units(a,b,c,tC,4)


Columns 1 through 8
0.0000 0.0000 0.0002 0.0031 0.0280 0.1560 0.6192 1.9181
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 8 9 10

>> p_trim = p_bar(index)


p_trim =
Columns 1 through 8
0.0000 0.0000 0.0002 0.0031 0.0280 0.1560 0.6192 1.9181
Columns 9 through 10
4.9318 10.9822

Also, know that instead of psat_max = p_bar(length(tC)) we could equiv-


alently 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 per-
forming 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 1 1 0 0

>> p_trim_l = p_bar(index_l)


p_trim =
Columns 1 through 6
200 Chapter 6 Functions of vectors

0.0000 0.0000 0.0002 0.0031 0.0280 0.1560


Columns 7 through 10
0.6192 1.9181 4.9318 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 ele-
ments 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 201

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. Specif-
ically, 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 . Fol-
lowing 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/
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 some-
thing 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 function res = error_func(x)


2 res = x.^2 - 2.*x - 3;
3 end
7.4 Nonlinear equations 205

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 ques-
tion, 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 some-
thing like “finding an equivalent relationship that is explicit in one of the vari-
ables.” 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
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 zero-
finding (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 ef-
ficient 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 function res = error_func(x)


2 res = x.^2 - 2.*x - 3;
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? 209

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:
210 Chapter 7 Zero-finding

1 function res = error_func(x)


2 y = x.^2 - 2.*x -3
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 211

When you write your own functions and use them yourself, it is easy for t Well, okay, there are ex-
mistakes to go undetected. But when you use your functions with MATLAB ceptions to the rule that
functions like fzero, you have to get it right! functions should always
Yet another thing that can go wrong: if you provide an interval for the initial assign values to their
output variables, includ-
guess and it doesn’t actually contain a root, you get
ing find_triples (List-
ing 5.15). Functions that
>> fzero(@error_func, [0,1]) don’t return a value are
sometimes called “com-
Error using fzero (line 274) mands,” because they do
The function values at the interval endpoints must differ in something (like display val-
sign. ues or generate a figure) but
either don’t have an output
variable or don’t make an
Or it may be that the interval contains a root, it is just not guaranteed per the assignment to it.
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.

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 t Note that in previous
zeros visually. If you have a function, like error_func that takes a single input semesters we would use
ezplot. However, the MAT-
variable and returns a single output variable, you can plot it with fplot:
LAB documentation now
indicates that ezplot is
>> fplot(@error_func, [-2,5]) not recommended, to use
fplot instead.
212 Chapter 7 Zero-finding

12

10

-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

-2

-4
-2 -1 0 1 2 3 4 5

Figure 7.3 fplot(@error_func, [-2,5],’-ro’)


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

-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

-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 when-
ever 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 vari-
able 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 215

>> 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 func-
tion 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 MAT-
LAB 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
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 % res = cheby6(x)
2 % Function to evaluate the 6th Chebyshev polynomial
3 function res = cheby6( x )
4 res = 32.*x.^(6) -48.*x.^(4) + 18.*x.^(2) -1;
5 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.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 (7.2)
3 3

π
Ww = ρ w g V = ρ w g (3r d 2 − d 3 ) (7.3)
3

Our criteria for buoyancy is Wd = Ww , giving


220 Chapter 7 Zero-finding

4 π
0.3ρ w g πr 3 = ρ w g (3r d 2 − d 3 ) (7.4)
3 3

which simplifies to

(0.3)(4)r 3 = (3r d 2 − d 3 ) (7.5)

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 % res = duck(x)
2 % Function to find the required depth of a duck to remain
3 % buoyant in water
4 function res = duck( x )
5 res = 0.3.*4.*10.^(3)-(3.*10.*x.^(2)-x.^(3));
6 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

-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
to pass extra parameters. 7.13 Anonymous function
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 func-
tions the workspace rules is a calculation you will need to perform often, so you decide to write a function
change slightly, which is M-file. This leads to
why I suggest to wait. The
other is to use global vari- Listing 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 func-
tions, 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 t Remember a script file is
middle of an exam and do not have the time to write a function file! We can nothing more than a doc-
ument containing a series
instead create an anonymous function in the Command Window . Repeating the
of commands you would
same calculations: otherwise execute in the
Command Window . When
>> crt = @(X) X.^(1/3); we run a script, we execute
commands line-by-line as
>> crt(9) if we had entered them in
ans = the Command Window . One
could therefore create a
2.0801
script that contains anony-
mous functions to save your
>> crt(27) work. Anonymous func-
ans = tions could also be used in
3 function files.

>> 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 han-
dle, @; 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,
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 function res = sin_fun(X)


2 a = 4;
3 b = 2;
4 c = 1;
5 res = a.*sin(b.*X)+c;
6 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])

-1

-2

-3
0 1 2 3 4 5 6

Figure 7.8 fplot(sfun,[0,2*pi])


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 parame-
ters. 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 anony-
mous 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 function res = sin_fun_2(X)


2 a = 4;
3 b = 2;
4 c = 2;
5 res = a.*sin(b.*X)+c;
6 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 anony-


mous 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 recollec-
tion 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


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
t What is meant by another file that contains MATLAB formatted data. Data can be written to or loaded from a
or new session? Well a new MAT-file using the save and load command respectfully. To save our anonymous
session would be if you
function sfun to a file sfun.mat, the command would be save sfun.mat sfun.
closed MATLAB and then
re-opened it later.
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 sub-
merged 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 ) (7.7)
3 3
Simplifying

(0.3)(4)r 3 = ρ(3r d 2 − d 3 ) (7.8)


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 % res = duck2(x,r,rho)
2 % Function to find the required depth of a duck to remain
3 % buoyant in water. duck2 requires that we pass parameters
4 % r (radius of duck) and rho (density of the liquid), in
5 % addition to the depth (x) which is what we will solve for.
6 function res = duck2( x,r,rho )
7 res = 0.3.*4.*r.^(3)-rho.*(3.*r.*x.^(2)-x.^(3));
8 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 t Remember, in Example 3.6
falls in the category of bracketing methods for zero-finding. To keep matters we wrote a script to com-
pute sin(x) using a Taylor
simple for now, think of the the bisection method in the context of Example 7.2,
series expansion. This is
our duck buoyancy problem. This will serve as a good starting point since there is what MATLAB does, and
only one zero (or root or solution to our problem). gave us an appreciation of
In general, our goal is to find the value of x = x ans for which f (x ans ) = 0. To the numerical error in the
one side of x ans , the function will be positive, on the other side it will be negative, calculation.
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 func-
tion 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 :

1
x ans = (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 calcula-
tion. (Note, since anything times anything is 0, you could just as well
check it f (x r ) f (x ans ) = 0.)
230 Chapter 7 Zero-finding

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.
t Another common fzero Third, we saw that with fzero we can specify either an initial guess or a range.
error is the failure to pro- How do you use the bisection method if only an initial guess is provided? Well, we
vide correct brackets to the
assume the guess is close to the root, so we can use this to guess an initial value of
zero. Likewise, if you pro-
vide a guess of location of
x l and x r . If you wanted to provide this functionality, you would need to decide
the zero that is very bad, how to estimate this range.
MATLAB will be unable to Enough talk, let’s do it!
find brackets.
7.17 Numerical methods: zero-finding 231

Listing 7.8 bisection_zero.m

1 % res = bisection_zero(fcn_handle,Bracket,maxit,tol)
2 % Function to perform bisection method to find the root
3 % of a function (evaluated/provided in fcn_handle)
4 % in the range xl to xr. maxit sets the maximum number
5 % of iterations and tol sets the tolerance for convergence.
6 % Bracket is a vector where the first element is xl and the
7 % second element is xr.
8 function res = bisection_zero(fcn_handle,Bracket,maxit,tol)
9 %hold on
10 % Start by un-packing Bracket into x1 and xr
11 xl = Bracket(1);
12 xr = Bracket(2);
13 % Check to make sure f(xl) and f(xr) have opposite signs.
14 % If not, don't do anything.
15 if fcn_handle(xl)*fcn_handle(xr) > 0
16 %f(xl) and f(xr) have the same sign
17 disp('f(xl) and f(xr) have the same sign. Exit.')
18 % else, f(xl) and f(xr) have the opposite sign,proceed
19 else
20 for i=1:maxit
21 % Estimate location of zero as midpoint between
22 % xl and xr
23 xans = 0.5*(xl+xr);
24 fxans = fcn_handle(xans);
25 fxl = fcn_handle(xl);
26 fxr = fcn_handle(xr);
27 % Check if we found our root
28 if fxans^(2) < tol^(2)
29 res = xans;
30 %i
31 break
32 % Otherwise, update our brackets
33 elseif fxl*fxans < 0
34 xr = xans;
35 elseif fxr*fxans < 0
36 xl = xans;
37 else
38 % We should never get here, but just in case
39 disp('Critical error. Exit')
40 disp('Current estimate:')
41 xans
42 fxans
43 break
232 Chapter 7 Zero-finding

44 end
45 % Check to see if we reached the maximum number of
46 % iterations. If we did, print the current estimates.
47 if i == maxit
48 disp('Reached maximum number of iterations. Exit')
49 disp('Current estimate:')
50 xans
51 fxans
52 end
53 %plot(i,xans,'ro')
54 end
55 end
56 end

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

8.5

7.5

6.5

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 Figure 7.10 Our initial estimate of
x l and x r that bracket our zero.
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.
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 235

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.
236 Chapter 7 Zero-finding

Listing 7.9 bisection_zero_2.m

1 % res = bisection_zero_2(fcn_handle,Bracket,maxit,tol)
2 % Function to perform bisection method to find the root
3 % of a function (evaluated/provided in fcn_handle)
4 % in the range xl to xr. maxit sets the maximum number
5 % of iterations and tol sets the tolerance for convergence.
6 % Bracket is a vector where the first element is xl and the
7 % second element is xr.
8 function [res,i,fxans] = bisection_zero_2(fcn_handle,Bracket,maxit,tol)
9 %hold on
10 % Start by un-packing Bracket into x1 and xr
11 xl = Bracket(1);
12 xr = Bracket(2);
13 % Check to make sure f(xl) and f(xr) have opposite signs.
14 % If not, don't do anything.
15 if fcn_handle(xl)*fcn_handle(xr) > 0
16 %f(xl) and f(xr) have the same sign
17 disp('f(xl) and f(xr) have the same sign. Exit.')
18 % else, f(xl) and f(xr) have the opposite sign,proceed
19 else
20 for i=1:maxit
21 % Estimate location of zero as midpoint between
22 % xl and xr
23 xans = 0.5*(xl+xr);
24 fxans = fcn_handle(xans);
25 fxl = fcn_handle(xl);
26 fxr = fcn_handle(xr);
27 % Check if we found our root
28 if fxans^(2) < tol^(2)
29 res = xans;
30 %i
31 break
32 % Otherwise, update our brackets
33 elseif fxl*fxans < 0
34 xr = xans;
35 elseif fxr*fxans < 0
36 xl = xans;
37 else
38 % We should never get here, but just in case
39 disp('Critical error. Exit')
40 disp('Current estimate:')
41 xans
42 fxans
43 break
7.17 Numerical methods: zero-finding 237

44 end
45 % Check to see if we reached the maximum number of
46 % iterations. If we did, print the current estimates.
47 if i == maxit
48 disp('Reached maximum number of iterations. Exit')
49 disp('Current estimate:')
50 xans
51 fxans
52 end
53 %plot(i,xans,'ro')
54 end
55 end
56 end

>> 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.
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 r f (x l ) − x l f (x l )
x ans =
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 239

Listing 7.10 linear_interpol_zerp.m

1 % res = linear_interpol_zero(fcn_handle,xl,xr,maxit,tol)
2 % Function to perform linear interpolation method to find the root
3 % of a function (evaluated/provided in fcn_handle)
4 % in the range xl to xr. maxit sets the maximum number
5 % of iterations and tol sets the tolerance for convergence.
6 % Bracket is a vector where the first element is xl and the
7 % second element is xr.
8 function [res,i,fxans] = linear_interpol_zero(fcn_handle,Bracket,maxit,tol)
9 %hold on
10 % Start by un-packing Bracket into x1 and xr
11 xl = Bracket(1);
12 xr = Bracket(2);
13 % Check to make sure f(xl) and f(xr) have opposite signs.
14 % If not, don't do anything.
15 if fcn_handle(xl)*fcn_handle(xr) > 0
16 %f(xl) and f(xr) have the same sign
17 disp('f(xl) and f(xr) have the same sign. Exit.')
18 % else, f(xl) and f(xr) have the opposite sign,proceed
19 else
20 for i=1:maxit
21 fxl = fcn_handle(xl);
22 fxr = fcn_handle(xr);
23 % Find our new guess of the root location
24 xans = (xr*fxl-xl*fxr)/(fxl-fxr);
25 % Evaluating our funciton at this new estimate of the root
26 fxans = fcn_handle(xans);
27 % Check if we found our root
28 if fxans^(2) < tol^(2)
29 res = xans;
30 %i
31 break
32 elseif fxl*fxans < 0
33 xr = xans;
34 elseif fxr*fxans < 0
35 xl = xans;
36 else
37 disp('Critical error. Exit')
38 break
39 end
40 if i == maxit
41 disp('Reached maximum number of iterations')
42 xans
43 fxans
240 Chapter 7 Zero-finding

44 end
45 %plot(i,xans,'ro')
46 end
47 end
48 end

>> 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 func-
tion handle to refer to MAT- >> fzero(@sin,[1,4])
LAB’s built-in function sin.
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 t For now, don’t worry about
with what we found using fzero. Formally, while C is a row vector (1 × 7), Sol is a the difference between a
column vector (6×1). So to select an element from Sol, we would use a command row and column vector.
like Both are vectors, so you can
select an element using a
single index. We will revisit
>> zero_1 = Sol(3)
row and column vectors
zero_1 = 0.9659 later.

or equivalently
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 Poly-


nomials.”

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.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])

-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

-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 there-
fore suspect that it has only one real root. Zooming in on the range 0 to 1:

>> fplot(@poly_error_func,[0,1])

-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 + 2.3338i
5.5561 - 2.3338i
1.0773 + 0.8606i
1.0773 - 0.8606i
0.5793 + 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:

B
log10 p sat = A −
T +C

Next, we need to re-write Antoine’s equation as an error function suitable for use
with fzero:

B
log10 p sat − A + =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 opera-
tions 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 solu-


tion.

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

l 1
∆p = f ρV 2 (7.11)
D2
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 = −2 log10 + p (7.12)
f 3.7 Re f
where ² is the pipe roughness, and Re is the Reynold’s number, defined as

ρV D
Re = (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

l 1
∆p = f ρV 2 (7.14)
D2
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 = −2 log10 + p (7.15)
f 3.7 Re f
where ² is the pipe roughness, and Re is the Reynold’s number, defined as

ρV D
Re = (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:
π¡
3r o d 2 − d 3
¢
V=
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 den-
sity, 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 tem-
perature 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

γ1 (T, x 1 ) p 1sat (T )
α (T, x 1 ) =
γ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 pres-
sure, 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:

B
log10 p sat = A −
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 A B C Tmin [◦ C] Tmax [[◦ C]


ethyl acetate 7.10179 1244.95 217.9 16 76
ethanol 8.20417 1642.89 230.3 –57 80

The activity coefficient can be modeled using the NRTL (non-random two-liquid)
equation:
¶2
G 21 τ12G 12
· µ ¸
ln γ1 = x 22 τ21 +
x 1 + x 2G 21 (x 2 + x 1G 12 )2
¶2
G 12 τ21G 21
· µ ¸
ln γ2 = x 12 τ12 +
x 2 + x 1G 12 (x 1 + x 2G 21 )2
where
g 12 − g 22 g 21 − g 11
τ12 = τ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 255

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/
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 6
3 5 7
4 9 2

If you don’t know the size of a matrix, you can use whos to display it:

>> whos
Name Size Bytes Class Attributes
M 3x3 72 double

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 259

>> size(D)
ans = 2 3

As you begin to work with row and column vectors, and matrices, remem-
ber 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 re-
member 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 MAT-
LAB’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 ele-
ments 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 4 5

>> Xt = X'
Xt = 1
2
3
4
5

>> size(X)
ans = 1 5

>> size(Xt)
ans = 5 1
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 con-
tinuous 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 t Also, while not discussed
the zeros of a single equation. For this case, the use of fsolve will look exactly here, fsolve can be ap-
plied to non-square systems
like fzero. Imagine we would like to solve the equation x 2 − 2x = 3. We begin by
of equations; that is, where
writing an error function, such that the function evaluates to zero at a solution: the number of equations
x 2 − 2x − 3 = 0. is greater than the number
of unknowns, or when the
Listing 8.1 error_func.m number of unknowns is
greater than the number of
1 function res = error_func(X) equations. For these cases,
2 res = X.^(2)-2.*X-3;
fsolve acts as a minimizer.
3 end
I hope to add a chapter on
the topic in the future.
Let’s assume we are searching for the root over the range 0 < x < 5. Solving using
fzero: t Note that fsolve is part of
>> fzero(@error_func,[0,5]) MATLAB’s Optimization
ans = 3 Toolbox. If you obtain an
error message indicating
With fzero we could alternatively provide an initial estimate of the zero location that the command fsolve
is unknown, you likely did
instead of a range to search.
not include the Optimiza-
>> fzero(@error_func,4) tion Toolbox in your initial
ans = 3.0000 MATLAB installation.

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
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 remem-
ber 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? 265

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:


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? 267

>> 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.
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:

p
−b ± b 2 − 4ac
x=
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: 'trust-region-dogleg'
CheckGradients: 0
Display: 'final'
FiniteDifferenceStepSize: 'sqrt(eps)'
FiniteDifferenceType: 'forward'
FunctionTolerance: 1.0000e-06
MaxFunctionEvaluations: '100*numberOfVariables'
MaxIterations: 400
OptimalityTolerance: 1.0000e-06
OutputFcn: []
PlotFcn: []
SpecifyObjectiveGradient: 0
StepTolerance: 1.0000e-06
TypicalX: 'ones(numberOfVariables,1)'
UseParallel: 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: 'trust-region-dogleg'
CheckGradients: 0
Display: 'final'
FiniteDifferenceStepSize: 'sqrt(eps)'
FiniteDifferenceType: 'forward'
MaxFunctionEvaluations: '100*numberOfVariables'
MaxIterations: 400
OutputFcn: []
PlotFcn: []
SpecifyObjectiveGradient: 0
StepTolerance: 1.0000e-06
TypicalX: 'ones(numberOfVariables,1)'
UseParallel: 0

Show options not used by current Algorithm ('trust-region-dogleg')

Now when we call fsolve we can provide an additional input argument corre-
sponding 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: 'trust-region-dogleg'
CheckGradients: 0
FiniteDifferenceStepSize: 'sqrt(eps)'
FiniteDifferenceType: 'forward'
MaxFunctionEvaluations: '100*numberOfVariables'
MaxIterations: 400
OutputFcn: []
PlotFcn: []
SpecifyObjectiveGradient: 0
StepTolerance: 1.0000e-06
TypicalX: 'ones(numberOfVariables,1)'
UseParallel: 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


hope to add to the text in
8.8 One final fsolve note the near future.

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.
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. (Remem-
ber 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 equa-
tions 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 -3.3636
0 1.0000 0 2.9091
0 0 1.0000 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 ma-
trix. 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 t This corresponds to having
could have more equations than unknowns or more unknowns than equations. more unknowns than equa-
tions. Maybe a better way
The more common scenario is the system of equations appears square, but two or
to phrase is would be more
more of the equations are linear combinations of each other. What if we slightly unknown than linearly inde-
changed our system of equations to the following: pendent equations.

2x + 3y = 2 (8.9)

2x + 4y + z = 6 (8.10)

y +z =4 (8.11)
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 t Let A and B correspond to
system of linear equations is left division. Let us again consider the system of matrices. Then (right divi-
sion) A/B = AB −1 . On the
equations given by eqs. 8.1, 8.2, and 8.3. We can re-write the system in the general
other hand, (left division)
form Ax = b, where A is a (square, n × n) matrix of the coefficients, b is a column A\B = A −1 B
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:
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 desig-
nated 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 con-
sider 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, re-
spectively, 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, respec-
tively. The pure component vapor pressure is only a function of temperature, and
is commonly computed using Antoine’s equation:

B
log10 p sat = A − (8.14)
T +C
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 A B C Tmin Tmax


hexane 7.01051 1246.33 232.988 –95 235
cyclohexane 6.85146 1206.47 223.136 7 81
cyclohexane 7.09926 1380.54 246.526 29 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 manip-
ulate 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 281

Listing 8.6 raoult_tx.m


1 % function res = raoult_tx(Ant,t_C,x1)
2 %
3 % Function to compute binary vapor liquid equilibrium using Raoult's law.
4 % Temperature and liquid mole fraction are known, and we solve for
5 % the vapor phase mole fraction and pressure.
6 %
7 % Inputs: Ant is a matrix containing the Antoine parameters for
8 % component 1 and 2, respectively. The first row corresponds to
9 % parameters for component 1, and the second row to parameters
10 % for component 2, where there are three columns corresponding
11 % to A, B, and C. The units are such that temperature is in C
12 % and pressure is in mmHg.
13 % t_C is the temperature of interest in degrees C
14 % x1 is the liquid phase mole fraction
15 %
16 % Outputs: res is a vector of length 2, where the first element is
17 % y1 and the second element is p. p will be in units of kPa
18 function res = raoult_tx(Ant,t_C,x1)
19 % Calculate the vapor pressure of component 1 and 2 in Pa
20 psat_1 = antoine(Ant(1,:),t_C);
21 psat_2 = antoine(Ant(2,:),t_C);
22
23 % Next, we need to set-up our error functions for use with fsolve.
24 % Remember fsolve expects an error function with a single input and
25 % single output variable. Here I have written a general error function
26 % for solving tx problems where the liquid mole fraction, x1, and the
27 % vapor pressure of each component are also taken as input. Remember we
28 % can pass this additional information using anonymous functions
29 error_func = @(Y) raoult_error_tx(x1,psat_1,psat_2,Y);
30
31 % Now we can use fsolve to solve for the vapor mole fraction and
32 % pressure. The order of the variables must agree with that used in the
33 % error function. We will also need to provide initial estimates. A
34 % safe estimate would be that y1 = 0.5, or somewhere in between 0 an 1,
35 % and for p, try x1*psat_1+x2*psat_2. You can play around with this.
36 y10 = 0.5;
37 p0 = x1*psat_1+(1-x1)*psat_2;
38 % Note that is the solution for p for the special case of Raoult's law,
39 % but not in general. So this will make a good initial guess for the
40 % general case.
41 res = fsolve(error_func,[y10,p0]);
42 %
43 % Converting the pressure to kPa before returning
44 res(2) = res(2)/1000;
45 end
46
47 % function res = antoine(Ant,t_C)
48 %
49 % Helper function to compute vapor pressure using Antoine's equation.
50 % Temperature is in degrees C, and pressure is in mmHg
51 %
52 % Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
53 % t_C is the temperature in degrees C
282 Chapter 8 Systems of Equations

54 % Outputs: res is the vapor pressure in units of Pa.


55 %
56 function res = antoine(Ant,t_C)
57 % un-packing the vector of Antoine parameters
58 a = Ant(1);
59 b = Ant(2);
60 c = Ant(3);
61 % Calculating the vapor pressure. Temperature is in C and pressure in
62 % mmHg
63 p_mmHg = 10^( a-b/(t_C+c) );
64 % Converting to units of Pa (SI units) and returning
65 res = p_mmHg*(101.325e3/760);
66 end
67
68 % function res = raoult_error_tx(x1,psat_1,psat_2,Y)
69 %
70 % The error function for solving Raoult's law for binary vapor liquid
71 % equilibrium. Everything is in SI units.
72 %
73 function res = raoult_error_tx(x1,psat_1,psat_2,Y)
74 % un-packing Y
75 y1 = Y(1);
76 p = Y(2);
77 % Relating y1 and y2, and x1 and x2. This will make my code
78 % more readible
79 y2 = 1-y1;
80 x2 = 1-x1;
81 % Our error functions
82 res1 = y1*p - x1*psat_1;
83 res2 = y2*p - x2*psat_2;
84 % packing up our error functions
85 res = [res1,res2];
86 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 283

Listing 8.7 hexane_cyclohexane_tx.m


1 %
2 % function res = hexane_cyclohexane_tx(t_C, x1)
3 %
4 % Function to compute p and y1 using Raoult's law for the binary system
5 % hexane(1)/cyclohexane(2) at a given temperature in C (t_C) and liquid
6 % phase mole fraction (x1)
7
8 function res = hexane_cyclohexane_tx(t_C, x1)
9
10 % Antoine parameters where T is in units C and p is in units of mmHg.
11 %
12 % Parameters for hexane(1) valid over the range -95 to 235 C
13 Ant_1 = [7.01051,1246.33,232.988];
14 % Parameters for cyclohexane(2) valid over the range 29 to 280 C
15 Ant_2 = [7.09926,1380.54,246.526];
16 %
17 % Packing the Antoine parameters to a matrix as used as an input to
18 % raoult_tx
19 Ant = [Ant_1; Ant_2];
20
21 % Performing Raoult's law calculation
22 res = raoult_tx(Ant,t_C,x1);
23
24 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.
284 Chapter 8 Systems of Equations

Listing 8.8 raoult_tx_2.m


1 % function res = raoult_tx_2(Ant,t_C,x1)
2 %
3 % Function to compute binary vapor liquid equilibrium using Raoult's law.
4 % Temperature and liquid mole fraction are known, and we solve for
5 % the vapor phase mole fraction and pressure. In this version we solve the
6 % system of equations analytically.
7 %
8 % Inputs: Ant is a matrix containing the Antoine parameters for
9 % component 1 and 2, respectively. The first row corresponds to
10 % parameters for component 1, and the second row to parameters
11 % for component 2, where there are three columns corresponding
12 % to A, B, and C. The units are such that temperature is in C
13 % and pressure is in mmHg.
14 % t_C is the temperature of interest in degrees C
15 % x1 is the liquid phase mole fraction
16 %
17 % Outputs: res is a vector of length 2, where the first element is
18 % y1 and the second element is p. p will be in units of kPa
19 function res = raoult_tx_2(Ant,t_C,x1)
20 % Calculate the vapor pressure of component 1 and 2 in Pa
21 psat_1 = antoine(Ant(1,:),t_C);
22 psat_2 = antoine(Ant(2,:),t_C);
23
24 % Next, perform a bubble p calculation to find p in Pa
25 %
26 p_Pa = x1*psat_1 + (1-x1)*psat_2;
27
28 % Then we can solve for y1
29 y1 = x1*psat_1/p_Pa;
30
31 % Last, convert the pressure to kPa, then pack up y1 and p and return
32 p_kPa = p_Pa/1000;
33 res = [y1,p_kPa];
34
35 end
36
37 % function res = antoine(Ant,t_C)
38 %
39 % Helper function to compute vapor pressure using Antoine's equation.
40 % Temperature is in degrees C, and pressure is in mmHg
41 %
42 % Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
43 % t_C is the temperature in degrees C
44 % Outputs: res is the vapor pressure in units of Pa.
45 %
46 function res = antoine(Ant,t_C)
47 % un-packing the vector of Antoine parameters
48 a = Ant(1);
49 b = Ant(2);
50 c = Ant(3);
51 % Calculating the vapor pressure. Temperature is in C and pressure in
52 % mmHg
53 p_mmHg = 10^( a-b/(t_C+c) );
8.13 Application: Raoult’s Law 285

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 % function res = hexane_cyclohexane_tx_2(t_C, x1)
3 %
4 % Function to compute p and y1 using Raoult's law for the binary system
5 % hexane(1)/cyclohexane(2) at a given temperature in C (t_C) and liquid
6 % phase mole fraction (x1)
7
8 function res = hexane_cyclohexane_tx_2(t_C, x1)
9
10 % Antoine parameters where T is in units C and p is in units of mmHg.
11 %
12 % Parameters for hexane(1) valid over the range -95 to 235 C
13 Ant_1 = [7.01051,1246.33,232.988];
14 % Parameters for cyclohexane(2) valid over the range 29 to 280 C
15 Ant_2 = [7.09926,1380.54,246.526];
16 %
17 % Packing the Antoine parameters to a matrix as used as an input to
18 % raoult_tx
19 Ant = [Ant_1; Ant_2];
20
21 % Performing Raoult's law calculation
22 res = raoult_tx_2(Ant,t_C,x1);
23
24 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).
286 Chapter 8 Systems of Equations

Listing 8.10 raoult_px.m


1 % function res = raoult_px(Ant,p_kPa,x1)
2 %
3 % Function to compute binary vapor liquid equilibrium using Raoult's law.
4 % Pressure and liquid mole fraction are known, and we solve for
5 % the vapor phase mole fraction and temperature.
6 %
7 % Inputs: Ant is a matrix containing the Antoine parameters for
8 % component 1 and 2, respectively. The first row corresponds to
9 % parameters for component 1, and the second row to parameters
10 % for component 2, where there are three columns corresponding
11 % to A, B, and C. The units are such that temperature is in C
12 % and pressure is in mmHg.
13 % p_kPa is the pressure of interest in kPa
14 % x1 is the liquid phase mole fraction
15 %
16 % Outputs: res is a vector of length 2, where the first element is
17 % y1 and the second element is T. T will be in units of C
18 function res = raoult_px(Ant,p_kPa,x1)
19
20 % Next, we need to set-up our error functions for use with fsolve.
21 % Remember fsolve expects an error function with a single input and
22 % single output variable. Here I have written a general error function
23 % for solving px problems where the liquid mole fraction, x1, the pressure
24 % and the Antoine parameters are also taken as input.
25 % The Antoine parameters are essential because when we guess a value of
26 % temperature each iteration, we will need to compute the
27 % corresponding pure component vapor pressure. Remember we
28 % can pass this additional information using anonymous functions
29 error_func = @(Y) raoult_error_px(x1,p_kPa,Ant,Y);
30
31 % Now we can use fsolve to solve for the vapor mole fraction and
32 % temperatute. The order of the variables must agree with that used in the
33 % error function. We will also need to provide initial estimates. A
34 % safe estimate would be that y1 = 0.5, or somewhere in between 0 an 1,
35 % and for t, try 0.5*(tsat_1+tsat_2). You can play around with this.
36 %
37 % How do we get tsat_1 and tsat_2? It is the temperature where
38 % psat_1 = p and psat_2 = p.
39 %
40 y10 = 0.5;
41 tsat_1 = antoine_t(Ant(1,:),p_kPa);
42 tsat_2 = antoine_t(Ant(2,:),p_kPa);
43 t0 = 0.5*(tsat_1+tsat_2);
44 res = fsolve(error_func,[y10,t0]);
45 end
46
47 % function res = antoine(Ant,t_C)
48 %
49 % Helper function to compute vapor pressure using Antoine's equation.
50 % Temperature is in degrees C, and pressure is in mmHg
51 %
52 % Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
53 % t_C is the temperature in degrees C
8.13 Application: Raoult’s Law 287

54 % Outputs: res is the vapor pressure in units of Pa.


55 %
56 function res = antoine(Ant,t_C)
57 % un-packing the vector of Antoine parameters
58 a = Ant(1);
59 b = Ant(2);
60 c = Ant(3);
61 % Calculating the vapor pressure. Temperature is in C and pressure in
62 % mmHg
63 p_mmHg = 10^( a-b/(t_C+c) );
64 % Converting to units of Pa (SI units) and returning
65 res = p_mmHg*(101.325e3/760);
66 end
67
68 % function res = antoine_t(Ant,p_kPa)
69 %
70 % Helper function to compute saturation temperature using Antoine's equation.
71 % Temperature is in degrees C, and pressure is in mmHg. The input
72 % pressure is in kPa, so we will need to convert.
73 %
74 % Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
75 % p_kPa is the pressure in kPa
76 % Outputs: res is the temperature in C.
77 %
78 function res = antoine_t(Ant,p_kPa)
79 % un-packing the vector of Antoine parameters
80 a = Ant(1);
81 b = Ant(2);
82 c = Ant(3);
83 % Convert pressure to mmHg
84 p_mmHg = p_kPa*1000*(760/101.325e3);
85 % Calculating the vapor pressure. Temperature is in C and pressure in
86 % mmHg
87 res = -b/(log10(p_mmHg)-a) - c;
88 end
89 % function res = raoult_error_tx(x1,psat_1,psat_2,Y)
90 %
91 % The error function for solving Raoult's law for binary vapor liquid
92 % equilibrium. Everything is in SI units.
93 %
94 function res = raoult_error_px(x1,p_kPa,Ant,Y)
95 % un-packing Y
96 y1 = Y(1);
97 t_C = Y(2);
98 % with this T, calculate the vapor pressure
99 % Calculate the vapor pressure of component 1 and 2 in Pa
100 psat_1 = antoine(Ant(1,:),t_C);
101 psat_2 = antoine(Ant(2,:),t_C);
102 % Converting our pressure from kPa to Pa
103 p_Pa = p_kPa*1000;
104 % Relating y1 and y2, and x1 and x2. This will make my code
105 % more readible
106 y2 = 1-y1;
107 x2 = 1-x1;
108 % Our error functions
288 Chapter 8 Systems of Equations

109 res1 = y1*p_Pa - x1*psat_1;


110 res2 = y2*p_Pa - x2*psat_2;
111 % packing up our error functions
112 res = [res1,res2];
113 end

Listing 8.11 hexane_cyclohexane_px.m


1 %
2 % function res = hexane_cyclohexane_px(p_kPa, x1)
3 %
4 % Function to compute T and y1 using Raoult's law for the binary system
5 % hexane(1)/cyclohexane(2) at a given pressure in kPa (p_kPa) and liquid
6 % phase mole fraction (x1). The resulting temperature will be in C.
7
8 function res = hexane_cyclohexane_px(p_kPa, x1)
9
10 % Antoine parameters where T is in units C and p is in units of mmHg.
11 %
12 % Parameters for hexane(1) valid over the range -95 to 235 C
13 Ant_1 = [7.01051,1246.33,232.988];
14 % Parameters for cyclohexane(2) valid over the range 29 to 280 C
15 Ant_2 = [7.09926,1380.54,246.526];
16 %
17 % Packing the Antoine parameters to a matrix as used as an input to
18 % raoult_tx
19 Ant = [Ant_1; Ant_2];
20
21 % Performing Raoult's law calculation
22 res = raoult_px(Ant,p_kPa,x1);
23
24 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 289

Listing 8.12 raoult_px_2.m


1 % function res = raoult_px_2(Ant,p_kPa,x1)
2 %
3 % Function to compute binary vapor liquid equilibrium using Raoult's law.
4 % Pressure and liquid mole fraction are known, and we solve for
5 % the vapor phase mole fraction and temperature. In this version of the
6 % code we will solve the system of equations sequentially.
7 %
8 % Inputs: Ant is a matrix containing the Antoine parameters for
9 % component 1 and 2, respectively. The first row corresponds to
10 % parameters for component 1, and the second row to parameters
11 % for component 2, where there are three columns corresponding
12 % to A, B, and C. The units are such that temperature is in C
13 % and pressure is in mmHg.
14 % p_kPa is the pressure of interest in kPa
15 % x1 is the liquid phase mole fraction
16 %
17 % Outputs: res is a vector of length 2, where the first element is
18 % y1 and the second element is T. T will be in units of C
19 function res = raoult_px_2(Ant,p_kPa,x1)
20
21 % Next, we need to set-up our error function for use with fzero.
22 % Remember fzero expects an error function with a single input and
23 % single output variable. Here I have written a general error function
24 % for solving px problems where the liquid mole fraction, x1, the pressure
25 % and the Antoine parameters are also taken as input.
26 % The Antoine parameters are essential because when we guess a value of
27 % temperature each iteration, we will need to compute the
28 % corresponding pure component vapor pressure. Remember we
29 % can pass this additional information using anonymous functions
30 error_func = @(t) raoult_error_px(x1,p_kPa,Ant,t);
31
32 % Now we can use fzero to solve for the temperature in C.
33 % We will also need to provide initial estimate of T or a search range.
34 % A tempting search range is between tsat_1 and tsat_2. However, I
35 % avoid this as it could fail if in the future, when this function is
36 % adapted for a non-ideal system, for an azeotropic system. I instead
37 % use an inital estimate of 0.5*(tsat_1+tsat_2). You can play around
38 % with this.
39 %
40 % How do we get tsat_1 and tsat_2? It is the temperature where
41 % psat_1 = p and psat_2 = p.
42 %
43 tsat_1 = antoine_t(Ant(1,:),p_kPa);
44 tsat_2 = antoine_t(Ant(2,:),p_kPa);
45 t0 = 0.5*(tsat_1+tsat_2);
46 t_C = fzero(error_func,t0);
47
48 % Now that we have t_C, solve for psat_1 in Pa
49 psat_1 = antoine(Ant(1,:),t_C);
50 % And now directly solve for y1. We will need to convert p from kPa to
51 % Pa
52 y1 = x1*psat_1/(p_kPa*1000);
53
290 Chapter 8 Systems of Equations

54 % Now packing up y1 and t_C to be returned


55 res = [y1,t_C];
56 end
57
58 % function res = antoine(Ant,t_C)
59 %
60 % Helper function to compute vapor pressure using Antoine's equation.
61 % Temperature is in degrees C, and pressure is in mmHg
62 %
63 % Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
64 % t_C is the temperature in degrees C
65 % Outputs: res is the vapor pressure in units of Pa.
66 %
67 function res = antoine(Ant,t_C)
68 % un-packing the vector of Antoine parameters
69 a = Ant(1);
70 b = Ant(2);
71 c = Ant(3);
72 % Calculating the vapor pressure. Temperature is in C and pressure in
73 % mmHg
74 p_mmHg = 10^( a-b/(t_C+c) );
75 % Converting to units of Pa (SI units) and returning
76 res = p_mmHg*(101.325e3/760);
77 end
78
79 % function res = antoine_t(Ant,p_kPa)
80 %
81 % Helper function to compute saturation temperature using Antoine's equation.
82 % Temperature is in degrees C, and pressure is in mmHg. The input
83 % pressure is in kPa, so we will need to convert.
84 %
85 % Inputs: Ant is a vector of length 3 of Antoine parameters A, B and C
86 % p_kPa is the pressure in kPa
87 % Outputs: res is the temperature in C.
88 %
89 function res = antoine_t(Ant,p_kPa)
90 % un-packing the vector of Antoine parameters
91 a = Ant(1);
92 b = Ant(2);
93 c = Ant(3);
94 % Convert pressure to mmHg
95 p_mmHg = p_kPa*1000*(760/101.325e3);
96 % Calculating the vapor pressure. Temperature is in C and pressure in
97 % mmHg
98 res = -b/(log10(p_mmHg)-a) - c;
99 end
100
101 % function res = raoult_error_tx(x1,psat_1,psat_2,t_C)
102 %
103 % The error function for solving for T via bubble p calculation using
104 % Raoult's law for binary vapor liquid equilibrium. Everything is in SI
105 % units, except T which is in C.
106 %
107 function res = raoult_error_px(x1,p_kPa,Ant,t_C)
108 % With this T, calculate the vapor pressure
8.13 Application: Raoult’s Law 291

109 % Calculate the vapor pressure of component 1 and 2 in Pa


110 psat_1 = antoine(Ant(1,:),t_C);
111 psat_2 = antoine(Ant(2,:),t_C);
112 % Converting our pressure from kPa to Pa
113 p_Pa = p_kPa*1000;
114 % Our bubble p error function
115 res = p_Pa - (x1*psat_1+(1-x1)*psat_2);
116 end

Listing 8.13 hexane_cyclohexane_px_2.m


1 %
2 % function res = hexane_cyclohexane_px_2(p_kPa, x1)
3 %
4 % Function to compute T and y1 using Raoult's law for the binary system
5 % hexane(1)/cyclohexane(2) at a given pressure in kPa (p_kPa) and liquid
6 % phase mole fraction (x1). The resulting temperature will be in C.
7
8 function res = hexane_cyclohexane_px_2(p_kPa, x1)
9
10 % Antoine parameters where T is in units C and p is in units of mmHg.
11 %
12 % Parameters for hexane(1) valid over the range -95 to 235 C
13 Ant_1 = [7.01051,1246.33,232.988];
14 % Parameters for cyclohexane(2) valid over the range 29 to 280 C
15 Ant_2 = [7.09926,1380.54,246.526];
16 %
17 % Packing the Antoine parameters to a matrix as used as an input to
18 % raoult_tx
19 Ant = [Ant_1; Ant_2];
20
21 % Performing Raoult's law calculation
22 res = raoult_px_2(Ant,p_kPa,x1);
23
24 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.
292 Chapter 8 Systems of Equations

8.14 Application: CSTR


8.14.1 CSTR Basics
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
t Non-steady-state behav- reactor, so we can solve our mole balance independent of our energy balance. It
ior is something that you is common to look at cases when this is not the case, which requires solving your
need to worry about when
mass and energy balance simultaneously. But that will be saved for your Kinetics
starting-up or shutting-
down a process, or when
and Reactor Design course. In the spirit of promoting free texts, when I taught
there is a disturbance in Kinetics and Reactor Design I used the free text “A First Course on Kinetics and
your system. 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 composi-
tion 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 expres-


sion therefore reduces to:

INPUT + GENERATION = OUTPUT

t This is a homogeneous, Consider an arbitrary species A involved in a chemical reaction. We will as-
bulk phase reaction. The sume that the only way a mole of A can be generated/consumed is via a chemical
rate of reaction is therefore
reaction. If we consider here just a single chemical reaction taking place:
made intensive by dividing
by the volume of the system.
GENERATION = ν A V r
If we double the size of the
system we expect that rate where ν A is the stoichiometic coefficient of A, ν A r is the per unit volume rate of
of generation/consump-
generation/consumption of A, and V is the volume of the system. With this our
tion to double, so volume
is the natural scale factor.
mole balance design equation becomes:
However, there are other
INPUT + ν A V r = OUTPUT
conventions. A heteroge-
neous, catalytic reaction
Last, we designate our inlet molar flow rate of component A as ṅ 0A and the outlet
takes place at the surface
of a solid catalyst. (It is het- molar flow rate of component A as ṅ A . This leads to the final expression:
erogeneous because the
ṅ 0A + ν A V r = ṅ A
catalyst is solid and in the
presence of either a liquid
Or more commonly:
or gas.) For this case, the
rate of reaction is made in- ν A V r = ṅ A − ṅ 0A
tensive by dividing by the A common measure of the reaction progress is the fractional conversion. The
surface area of the catalyst.
fractional conversion of A may be computed as
This is again the natural
scale factor because if we ṅ 0A − ṅ A
double the surface area fA =
of the catalyst, we expect ṅ 0A
the rate of generation/con-
It tells us what fraction of A in the inlet has been consumed.
sumption to double.

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 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
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 t Also note here that having
takes place in the aqueous phase. The initial concentration of A is rather dilute, a dilute solution is a main
reason we can assume the
2 moles per liter of water. There are approximately 55.35 moles of water per
system is isothermal. There
liter, so this corresponds to a mole fraction of just 0.035. And since it is a 1 to 1
is an excess amount of wa-
reaction (for every mole of B created a mole of A is consumed), the total molar ter present that serves as a
concentration of A and B will remain the same (a small value). Therefore, it is heat bath
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 concentra-
tions:
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
τ=

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
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 isother-
mal 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 state-
ment 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:

ṅ 0A − ṅ A C A0 −C A
fA = = (8.17)
ṅ 0A C A0

Equation (8.16) and 8.17 corresponds to a system of two equation with two un-
knowns 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 % Function to solve for the volume of a CSTR to acheive a fractional
2 % conversion of 0.8. I will hardcode everything for the specific problem
3 % at hannd. You need just call the function to solve.
4 %
5 function res = cstr_single_v()
6
7 % First we will list the provided parameters
8
9 % Pre-exponential term, 1/s
10 k0 = 2.4e8;
11 % Activation energy, kcal/mol
12 ea_kcal = 15.3;
13 % Converting the activation energy to J/mol
14 ea_j = ea_kcal*1000*4.184;
15 % Molar gas constant in J/(mol K)
16 rg = 8.314;
17 % Temperature in K
18 t_K = 300;
19 % volumetric flow rate, L/hour
20 v_Lh = 1200;
21 % Converting the volumetric flow rate to L/s
22 v_Ls = v_Lh/3600;
23 % Initial concentration of A, mol/L
24 cA0 = 2;
25 % Fractional conversion
26 fA = 0.8;
27 % Stoichiometric coefficient of A
28 nuA = -1;
29
30 % Compute the rate constant
31 k = k0*exp(-ea_j/(rg*t_K));
32
33 % We need to create an anonymous function to pass to our error
34 % function: v_Ls, cA0, fA, nuA, k
35 cstr_error_func = @(CV) cstr_mole_balance(CV,v_Ls,cA0,fA,nuA,k);
36
37 % Using fsolve to solve for cA and V. I will use an initial estimate of
38 % cA = 1 M and V = 10 L. If fsolve fails we can try and tweak these.
39 res = fsolve(cstr_error_func,[1,10]);
40
41 end
42
43 % Error function corresponding to our mole balance design equation
44 % and fractional conversion. The first argument will be a vector, CV, where
45 % the first element is cA and the second element is v, the two variables we
46 % wish to solve for using fsolve. The others are parameters that will be
47 % passed using an anonymous function.
48 function res = cstr_mole_balance(CV,v_Ls,cA0,fA,nuA,k)
49 % Un-packing
50 cA = CV(1);
51 v = CV(2);
52
53 % The rate expressions
8.14 Application: CSTR 299

54 r = k*cA;
55
56 % Now computing the error functions.
57 % The mole balance re-written as an error function
58 res1 = nuA*v*r-v_Ls*(cA-cA0);
59 res2 = fA - (cA0-cA)/cA0;
60
61 % Packing up the rate functions
62 res = [res1,res2];
63
64 end

>> 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 % Function to solve for the space time of a CSTR to acheive a fractional
2 % conversion of 0.8. Also, create a plot of the fractional version versus
3 % space time. I will hardcode everything for the specific problem
4 % at hannd. You need just call the function to solve.
5 %
6 function res = cstr_single_tau()
7
8 % First we will list the provided parameters
9
10 % Pre-exponential term, 1/s
11 k0 = 2.4e8;
12 % Activation energy, kcal/mol
13 ea_kcal = 15.3;
14 % Converting the activation energy to J/mol
15 ea_j = ea_kcal*1000*4.184;
16 % Molar gas constant in J/(mol K)
17 rg = 8.314;
18 % Temperature in K
19 t_K = 300;
20 % Initial concentration of A, mol/L
21 cA0 = 2;
22 % Stoichiometric coefficient of A
23 nuA = -1;
24
25 % Compute the rate constant
26 k = k0*exp(-ea_j/(rg*t_K));
27
28 % Using fsolve to solve for cA and tau to acheive a fractional
29 % conversion of 0.8. We will using an anonymous function to pass cA0,
30 % nuA, and k to the error function.
31 cstr_error_func_08 = @(CT) cstr_mole_balance_08(CT,cA0,nuA,k);
32 res = fsolve(cstr_error_func_08,[1,10]);
33 % Converting the computed space time to minutes.
34 res(2) = res(2)/60;
35
36 % Next, creating the desired plot of fractional conversion versus space
37 % time.
38
39 % Space time in minutes
40 Tau = linspace(0,240);
41 % Vector to store computed concentrations of A
42 CA = zeros(1,length(Tau));
43 for i = 1:length(Tau)
44 % Each iteration define an anonymous function to pass Tau(i), k,
45 % nuA, and cA0 to the error function.
46 cstr_error_func = @(cA) cstr_mole_balance(cA,Tau(i),k,nuA,cA0);
47 % For fzero, I will set bounds from 0 to cA0.
8.14 Application: CSTR 301

48 CA(i) = fzero(cstr_error_func,[0,cA0]);
49 end
50 % Computing the fractional conversion
51 FA = (cA0-CA)./cA0;
52
53 % Plotting
54 hold on
55 plot(Tau,FA,'-k')
56
57 % Just for fun, plot our point corresponding to a fractional conversion
58 % of 0.8.
59 plot(res(2),0.8,'ko')
60
61 xlabel('\tau [min]')
62 ylabel('f_{A}')
63 print('-depsc','cstr_single_tau.eps')
64
65 end
66
67 % Error function corresponding to our mole balance design equation
68 % and fractional conversion. The first input variable is a vector of length
69 % 2 containing cA and tau which we will solve for using fsolve. The
70 % remaining variables are parameters that will be passed via an anonymous
71 % function.
72 %
73 function res = cstr_mole_balance_08(CT,cA0,nuA,k)
74 % Un-packing
75 cA = CT(1);
76 tau = CT(2);
77
78 % The desired fractional conversion
79 fA = 0.8;
80
81 % The rate expressions
82 r = k*cA;
83
84 % Now computing the error functions.
85 % The mole balance re-written as an error function
86 res1 = nuA*tau*r-(cA-cA0);
87 res2 = fA - (cA0-cA)/cA0;
88
89 % Packing up the rate functions
90 res = [res1,res2];
91 end
92
93 % Error function corresponding to our mole balance design equation.
94 % In this version, tau will be specified and we will just solve for
95 % cA. The first input variable is cA which we will solve for using fzero,
96 % and the remaining variables are parameters that will be passed via an
97 % anonymous function.
98 %
99 function res = cstr_mole_balance(cA,tau,k,nuA,cA0)
100 % Converting the space time from minutes to seconds
101 t = tau*60;
102
302 Chapter 8 Systems of Equations

103 % The rate expressions


104 r = k*cA;
105
106 % Now computing the error functions.
107 % The mole balance re-written as an error function
108 res = nuA*t*r-(cA-cA0);
109 end

>> sol = cstr_single_tau


sol = 0.4000 38.9167

0.9

0.8

0.7

0.6
fA

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 303

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.


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 compo-
nent 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
r 1 = 1.12 × 102 min−1 exp
¡ ¢
CA
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 equa-
tions 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 equi-
librium. 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 sym-
bols. 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 307

p/kPa x1 y1
77.327 0.12500 0.17900
81.607 0.25000 0.33600
85.673 0.37500 0.46500
90.033 0.50000 0.59000
94.019 0.62500 0.70800
97.965 0.74200 0.80600
100.818 0.87500 0.90100
Here I have copied our table of Antoine parameters, where T is in ◦ C and p is in
mmHg.

species A B C Tmin Tmax


hexane 7.01051 1246.33 232.988 –95 235
cyclohexane 6.85146 1206.47 223.136 7 81
cyclohexane 7.09926 1380.54 246.526 29 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 (hex-
ane(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 x1 y1
353.87 0.00000 0.00000
352.65 0.08200 0.12100
352.15 0.12100 0.16700
351.35 0.16700 0.22600
350.50 0.22600 0.30000
349.90 0.27000 0.35300
349.55 0.30000 0.38400
348.85 0.35300 0.43800
348.55 0.38400 0.46800
347.85 0.43800 0.52300
346.85 0.52300 0.60200
346.05 0.59100 0.67100
345.20 0.67100 0.74000
344.55 0.74000 0.79200
344.00 0.79700 0.84000
343.55 0.84000 0.87700
343.20 0.87700 0.90400
341.85 1.00000 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 tempera-
ture 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. Con-
nect 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:

B
log10 p sat = A −
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:
¶2
G 21 τ12G 12
· µ ¸
ln γ1 = x 22 τ21 +
x 1 + x 2G 21 (x 2 + x 1G 12 )2
¶2
G 12 τ21G 21
· µ ¸
ln γ2 = x 12 τ12 +
x 2 + x 1G 12 (x 1 + x 2G 21 )2
where
g 12 − g 22 g 21 − g 11
τ12 = τ21 =
RT RT
and
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 A B C Tmin [◦ C] Tmax [◦ C]


ethyl acetate 7.10179 1244.95 217.9 16 76
ethanol 8.20417 1642.89 230.3 –57 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 composi-
tion 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 ordi-
nary 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

df
∀t : (t ) = a f (t )
dt
This is an ordinary differential equation (ODE) because all the derivatives in-
volved are taken with respect to the same variable. If the equation related deriva-
tives 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” con-
tains a number of excellent, freely available textbooks on the subject.1 For a quick
introduction or refresher geared toward engineering students, I would recom-
mend “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 t If you think of our linear
Swiss mathematician Leonhard Paul Euler (1707–1783). If you need help getting interpolation example (or
a first order Taylor series
excited about Euler’s method, watch the clip from the motion picture “Hidden
expansion), this is accu-
Figures” at this link. rate if the function is linear
Here’s a test to see if you are as smart as Euler. Let’s say that you arrive at time over the range ∆t . As ∆t be-
t and measure the current population, y, and the rate of change, r . What do you comes smaller and smaller,
think the population will be after some period of time ∆t has elapsed? all functions will look more
If you said y + r ∆t , congratulations! You just invented Euler’s method. This and more linear over the
estimate is based on the assumption that r is constant, but in general it’s not, so range.
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
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 time scalar variable
∆t time step scalar constant
y population scalar variable
r rate of change scalar variable
f The unknown function specified, function t → y
implicitly, by an ODE.
d f /d t The first time derivative of f function t → r
g A “rate function,” derived from
the ODE, that computes rate of function t , y → r
change for any t , y.
T a sequence of times, t , where sequence
we estimate f (t )
F a sequence of estimates for f (t ) 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 Oc- A limitation of Euler’s method is that the time step is constant from one iteration
tave, you will need to in- to the next. But some parts of the solution are harder to estimate than others; if
stall the “ode” package.
the time step is small enough to get the hard parts right, it is doing more work than
You will then need to add
pkg load ode to your
necessary on the easy parts. The ideal solution is to adjust the time step as you
scripts or execute it from go along. Methods that do that are called adaptive, and one of the best adaptive
the Command Window before methods is the Dormand-Prince pair of Runge-Kutta formulas. You don’t have to
using ode45. If you are un- know what that means, because the nice people at Mathworks have implemented
sure if you currently have it in a function called ode45. The ode stands for “ordinary differential equation
the ode package installed, [solver];” the 45 indicates that it uses a combination of 4th and 5th order formulas.
execute pkg list from the
Command Window .
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 )] (9.4)
dt
where t is time in days and f (t ) is the population at time t . a and ω are parame-
ters. 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 t Our bacteria growth equa-
food supply on reproduction. tion (eq. 9.1) and rat growth
equation (eq. 9.4) should
This equation specifies a relationship between a function and its derivative.
look very similar. In the rat
In order to estimate values of f numerically, we have to transform it into a rate problem we multiply a f by
function. The first step is to introduce a variable, y, as another name for f (t ) a term that oscillates with t
to model the change in food
df
(t ) = a y [1 + sin (ωt )] supply with seasons.
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 function res = rats(t, y)


2 a = 0.01;
3 omega = 2 * pi / 365;
4 res = a * y * (1 + sin(omega * t));
5 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
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
later examples, you might >> ode45(@rats, [0, 365], 2)
store the time interval and
the initial condition to vari-
ables, say t_range and ic, The first argument is a handle for the function that computes g , the rate. The
to clarify your code. 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 dis-
plays 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

50
Number of rats

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 319

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 function res = rats_general(t, y, a, omega)


2 %a = 0.01;
3 %omega = 2 * pi / 365;
4 res = a * y * (1 + sin(omega * t));
5 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 vari-
able. 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-')


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 popu-
lation? 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?


t Later in this chapter we When you solve an ODE analytically, the result is a function, f , that allows you
will see how we can make to compute the population, f (t ), for any value of t . When you solve an ODE
our numerical solution numerically, you get two vectors. You can think of these vectors as a discrete ap-
behave like an analytic so- proximation of the continuous function f : “discrete” because it is only defined for
lution, and how we can
certain values of t , and “approximate” because each value F i is only an estimate
use fzero to find the time
when we have a specific
of the true value f (t ). So those are the limitations of numerical solutions. The
value. Later, we will addi- primary advantage is that you can compute numerical solutions to ODEs that
tionally see how we can use don’t have analytic solutions, which is the vast majority of nonlinear ODEs.
MATLAB’s interpolating If you are curious to know more about how ode45 works, you can modify rats
function, interp1, along to display the points, (t , y), where ode45 evaluates g . Here is a simple version:
with ode45 too. Armed
with these tricks, you might
forget about analytical solu- Listing 9.3 rats_2.m
tions all together. 1 function res = rats_2(t, y)
2 plot(t, y, 'rx')
3 a = 0.01;
4 omega = 2 * pi / 365;
5 res = a * y * (1 + sin(omega * t));
6 end

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')

5.5

4.5

3.5

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 t This is similar to the restric-
first argument as a function call, and calls rats without providing arguments. tion of the number of input
variables that can be passed
>> ode45(rats, [0,365], 2) that we saw previously with
fzero and fsolve. We
Error using rats (line 4)
have already seen that we
Not enough input arguments. can get around this limi-
tation using anonymous
Again, the error message is confusing, because it looks like the problem is in rats. functions. Later we will see
You’ve been warned! The exception to this is if you use an anonymous function 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 vari-
ables, 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 prob-
lem (eq. 9.1):

g (t , y) = a y
You might be tempted to write this:

1 function res = rate_func(y) % WRONG


2 a = 0.1;
3 res = a * y;
4 end

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 function res = rate_func(t, y) % RIGHT


2 a = 0.1;
3 res = a * y;
4 end

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 function res = rats(t, y)


2 a = 0.01;
3 omega = 2 * pi / 365;
4 r = a * y * (1 + sin(omega * t)); % WRONG
5 end

And then call it from ode45, you get


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, t Computing speed and the
it might not be your fault: the problem you are trying to solve might be stiff.2 I efficiency of MATLAB keeps
improving, so this example
won’t give a technical explanation of stiffness here, except to say that for some
might not be too bad on
problems (over some intervals with some initial conditions) the time step needed
your computer. Note, when
to control the error is very small, which means that the computation takes a long MATLAB is struggling, a
time. Here’s one example: Stop icon will show up in
the bottom left corner of
df the plot window. You can
= f 2− f 3
dt click it to kill the process.
If you solve this ODE with the initial condition f (0) = δ over the interval from 0 to You can also click Ctrl + c
2/δ, with δ = 0.01, you should see something like this: from the command line to
kill a process. You may no-
2 The following discussion is based partly on an article from Mathworks available at https: tice Stop buttons in a few
//www.mathworks.com/company/newsletters/articles/stiff-differential-equations.html plots in this chapter where
we try computationally
intensive processes.
324 Chapter 9 Ordinary Differential Equations

Listing 9.4 stiff.m

1 function res = stiff(t, y)


2 res = y^(2)-y^(3);
3 end

>> delta = 0.01;


>> ode45(@stiff, [0,2/delta], delta)

1.2

0.8

0.6

0.4

0.2

0
0 50 100 150 200

Figure 9.3 ode45(@stiff,[0, 2/delta] delta)


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
t Again, when I first taught problem is easy to fix: instead of ode45 you can use ode23s, an ODE solver that
this course in Spring 2017 tends to perform well on stiff problems (that’s what the “s” stands for).
the calculation was very
In general, if you find that ode45 is taking a long time, you might want to try
slow. Now it is not and
ode45 currently works per- one of the stiff solvers. It won’t always solve the problem, but if the problem is
fectly fine. 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

dv
ma(t ) = m (t ) = mg (9.5)
dt
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

dv
ma(t ) = m (t ) = mg − γv(t ) (9.6)
dt
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 ) (9.7)
dt
subject to the initial condition

v(0) = 0 (9.8)

We could solve this expression using separation of variables and obtain the
analytic result

g³ ´
v(t ) = 1 − e −kt (9.9)
k
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 (9.10)
dt
dv
Next, replace d t (t ) with 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

1 function res = free_fall(t, y)


2 k=0.5;
3 g=9.81;
4 res = g-k*y;
5 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

12
position/m

10

0
0 5 10 15 20
t/s

Figure 9.4 ode45(@free_fall, [0,20], 0)


9.9 Examples 327

Okay, and now let’s compare to the analytic solution.

Listing 9.6 free_fall_soln.m

1 function res = free_fall_soln(t)


2 k = 0.5;
3 g = 9.81;
4 res = (g/k)*(1-exp(-k*t));
5 end

>> fplot(@free_fall_soln,[0,20],'-r')

20

18

16

14

12
position/m

10

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 ) (9.14)
dt
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
¡ ¢
(t ) = −ρQ(t ) (9.15)
dt

dV
(t ) = −Q = −av(t ) (9.16)
dt
where in the second expression we have canceled out ρ, and Q is the volu-
metric 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 (9.17)
2
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

dh ¢1/2 ¡ ¢1/2
h(t )1/2
¡
A (t ) = −a 2g h(t ) = −a 2g (9.19)
dt

¡ ¢1/2
dh −a 2g
(t ) = h(t )1/2 (9.20)
dt A
If we were to solve analytically using separation of variables, subject to the
initial condition h(0) = h 0 , the result is

¶2
t
µp
h(t ) = h0 − k (9.21)
2
¡ ¢1/2
where k = a 2g /A.
Let’s solve numerically. Start by replacing h(t ) with y

¡ ¢1/2
dh −a 2g
(t ) = y 1/2 (9.22)
dt A
dh
Next, replace d t (t ) with g (t , y)

¡ ¢1/2
−a 2g
g (t , y) = y 1/2 (9.23)
A
We can now write our function
Listing 9.7 tank_drain.m

1 function res = tank_drain(t,y)


2 %a = 10/(100^(2)); % 10 cm^2
3 %A = 5; % 5 m^2
4 %g = 9.81;
5 %k = a*sqrt(2*g)/A;
6 k = 0.4;
7 res = -k*sqrt(y);
8 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

12

height
10

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 function res = tank_drain_soln(t)


2 k = 0.4;
3 h0 = 20;
4 res = (sqrt(h0)-k*t./2).^(2);
5 end

>> fplot(@tank_drain_soln,[0,20],'-r')
9.10 Euler in action! 331

20

18

16

14

12
height

10

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 func-
tion 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

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 335

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
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 function res = rat_time(t)


2 t0 = 0; % initial time
3 tfinal = t; % final time
4 y0 = 2; % initial population
5 [T,Y] = ode45(@rats, [t0, tfinal], y0);
6 % now let's return the the population at t
7 res = Y(end);
8 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 function res = rat_time_2(t)


2 t0 = 0; % initial time
3 tfinal = t; % final time
4 y0 = 2; % initial population
5 [T,Y] = ode45(@rats, [t0, tfinal], y0);
6 % now let's return the the population at - 30
7 res = Y(end)-30;
8 end

>> 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 popu-
lation 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 an-
other 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. Addition-
ally, 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 function res = free_fall_time(t)


2 t0 = 0; % initial time
3 tfinal = t; % final time
4 y0 = 0; % initial velocity
5 % solving our ODE
6 [T,Y]=ode45(@free_fall,[t0,tfinal],y0);
7 res = Y(end);
8 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 accel-
eration) 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 function res = free_fall_time_2(t)


2 t0 = 0; % initial time
3 tfinal = t; % final time
4 y0 = 0; % initial velocity
5 % solving our ODE
6 [T,Y]=ode45(@free_fall,[t0,tfinal],y0);
7 res = Y(end)-15;
8 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 evalu-
ates 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 function res = tank_drain_time(t,yfinal)


2 t0 = 0; % initial time
3 tfinal = t; % final time
4 y0 = 20; % the initial heigh
5 [T,Y]=ode45(@tank_drain,[t0,tfinal],y0);
6 res = Y(end)-yfinal;
7 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 state-
ment 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 function res = coffee()
2 %
3 %x = 5 % teesting that coffee works
4 %
5 %rate_func(0,90) % testing that rate_func works
6 %
7 %t0 = 0; % initial time
8 %tfinal = 60*60; % final time is 60 minutes
9 %f0 =90; % initial temperature of the coffee
10 %ode45(@rate_func,[t0,tfinal],f0)
11 %
12 mix_func(90,8,20,1) % testing that mix_func works
13 end
14
15 function res = rate_func(t,y)
16 r = 0.001;% units of 1/s
17 et = 20; % room temperature in oC
18 res = -r*(y-et);
19 end
20
21 function res = mix_func(y1,v1,y2,v2)
22 res = (y1*v1+y2*v2)/(v1+v2);
23 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 % Case of just cooling coffee (no cream)
2 %
3 % For the purpose of this exercise, I will return two optional variables,
4 % the time when the coffee is safe to drink and the temperature at this
5 % time. I will make them optional so that if you only wish
6 % to return time and not the temperature, which you expect to be 60.
7 %
8 function [tsafe,ysafe] = coffee_0_fzero()
9 % Remember, my lower bracket needs to be greater than 0.
10 % The first thing MATLAB will do when I call fzero with brackets
11 % is evaluate my function at the end points. 0 is not allowed because
12 % it would lead to an ode45 call with an intial and final time of
13 % zero.
14 tsafe = fzero(@coffee_0_error_func,[0.1,700]);
15
16 % Calculating the temperature at this time to see how close fzero
17 % get's us to the actual value. I will just call our error function,
18 % adding a value of 60 back to the final result to get the temperature.
19 ysafe = coffee_0_error_func(tsafe)+60;
20
21 end
22
23 % Our error function
24 function res = coffee_0_error_func(t)
25 % the initial temp of the coffee in oC
26 y0 = 90;
27 % Now let's use ode45 to compute the temperature
28 % of our beverage over a period of time t. Remember the only
29 % times we know exactly when our function will be evaluate is at
30 % t0 and tfinal. So if we return the last element of our vector, it will
31 % be the value of our function at t.
32 t0 = 0;
33 tfinal = t;
34 [T,Y] = ode45(@rate_func,[t0,tfinal],y0);
35 % We want to choose t such that the final
36 % temperature is 60 oC. Remember fzero is looking
37 % for zeroes. Our solution is when we find t such
38 % that our error function returns a value of 0.
39 % So we therefore need to subtract 60 from our final
40 % temperature.
41 res = Y(end)-60;
42 end
43
44 function res = rate_func(t,y)
45 r = 0.001;% units of 1/s
46 et = 20; % room temperature in oC
47 res = -r*(y-et);
48 end
49
50 function res = mix_func(y1,v1,y2,v2)
51 res = (y1*v1+y2*v2)/(v1+v2);
52 end
9.11 Return of fzero 345

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.
346 Chapter 9 Ordinary Differential Equations

Listing 9.17 coffee_1_fzero.m


1 % Case of adding cream immediately
2 %
3 % For the purpose of this exercise, I will return two optional variables,
4 % the time when the coffee is safe to drink and the temperature at this
5 % time, so we can see how close our solution get's us to the desired
6 % temperature of 60 oC so that we can compare our numerical solution
7 % strategies. I will make them optional so that if you only wish
8 % to return time and not the temperature, which you expect to be 60,
9 % you can.
10 %
11 function [tsafe,ysafe] = coffee_1_fzero()
12 % Remember, my lower bracket needs to be greater than 0.
13 % The first thing MATLAB will do when I call fzero with brackets
14 % is evaluate my function at the end points. 0 is not allowed because
15 % it would lead to an ode45 call with an intial and final time of
16 % zero.
17 tsafe = fzero(@coffee_1_error_func,[0.1,700]);
18
19 % Calculating the temperature at this time to see how close fzero
20 % get's us to the actual value. I will just call our error function,
21 % adding a value of 60 back to the final result to get the temperature.
22 ysafe = coffee_1_error_func(tsafe)+60;
23 end
24
25 % Our error function
26 function res = coffee_1_error_func(t)
27 y1 = 90; % initial temp of coffee in oC
28 y2 = 20; % initial temp of cream in oC
29 v1 = 8; % initial volume of coffee in ounces
30 v2 = 1; % intiial volume of cream in ounces
31 % Now mixing the coffee and cream and computing
32 % the initial temp of the mixture
33 y0 = mix_func(y1,v1,y2,v2);
34 % Now let's use ode45 to compute the temperature
35 % of our beverage over a period of 60 minutes.
36 % I will store the results to a vector that we
37 % can search through for our solution.
38 t0 = 0;
39 tfinal = t;
40 [T,Y] = ode45(@rate_func,[t0,tfinal],y0);
41 % We want to choose t such that the final
42 % temperature is 60 oC. If we use fzero we
43 % need to look for a value of zero, so
44 % subtract 60.
45 res = Y(end)-60;
46 end
47
48 function res = rate_func(t,y)
49 r = 0.001;% units of 1/s
50 et = 20; % room temperature in oC
51 res = -r*(y-et);
52 end
53
9.11 Return of fzero 347

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.
348 Chapter 9 Ordinary Differential Equations

Listing 9.18 coffee_2_fzero.m


1 % Case of adding creat at time t
2 function [tsafe,ysafe] = coffee_2_fzero()
3 tsafe = fzero(@coffee_2,[0.1,3600]);
4 ysafe = coffee_2(tsafe)+60;
5 end
6
7 % Our error function
8 function res = coffee_2(t)
9 % Let the coffee (no cream) cool for t
10 % seconds
11 y0 = 90;
12 t0 = 0;
13 tfinal = t;
14 [T,Y] = ode45(@rate_func,[t0,tfinal],y0);
15 %
16 % Now add the cream to the coffee
17 y2 = 20; % temperature of the cream
18 v2 = 1; % volume of the cream in ounces
19 y1 = Y(end); % temperature ofthe coffee
20 v1 = 8; % volume of the coffee in ounces
21 yfinal = mix_func(y1,v1,y2,v2);
22 %
23 % Since fzero looks for zeroes, lets shift
24 % the temperature by 60 C
25 res = yfinal-60;
26 end
27
28 function res = rate_func(t,y)
29 r = 0.001;% units of 1/s
30 et = 20; % room temperature in oC
31 res = -r*(y-et);
32 end
33
34 function res = mix_func(y1,v1,y2,v2)
35 res = (y1*v1+y2*v2)/(v1+v2);
36 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 in-
creases). 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 349

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
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

n 0A − n A
fA =
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 concentra-
tion 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

C A0 −C A
fA =
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 353

9.13 Glossary
differential equation (DE): An equation that relates the derivatives of an un-
known 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 deriva-
tives.

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.
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

n 0A − n A
fA =
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 t Notice here I am using
model is governed by the following system of differential equations: Lagrange’s notation for
compactness when writing
systems of differential equa-
tions; R 0 = ddRt and F 0 = ddFt .
R 0 = aR − bRF
(The notation on the right-
F 0 = ebRF − cF hand side corresponds to
Leibniz’s notation. Newton’s
where dot notation also exists, but
is not as common in the
• R is the population of rabbits, undergraduate curriculum;
it is used to designate flow
• F is the population of foxes, rates, but there is never a
mention of Newton.) Note
• a is the natural growth rate of rabbits in the absence of predation, that Lagrange’s notation can
also take the form: R t = ddRt
355 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 function res = lotka(t, V)


2 % unpack the elements of V
3 r = V(1);
4 f = V(2);
5
6 % set the parameters
7 a = 0.1;
8 b = 0.01;
9 c = 0.1;
10 e = 0.2;
11
12 % compute the derivatives
13 drdt = a*r - b*r*f;
14 dfdt = e*b*r*f - c*f;
15
16 % pack the derivatives into a vector
17 res = [drdt; dfdt];
18 end

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 re-
member what’s what. It also makes the third paragraph, where we compute the
10.1 Lotka-Voltera 357

derivatives, resemble the mathematical equations we were given, which helps


prevent errors.
The second paragraph sets the parameters that describe the reproductive t As we have seen previ-
rates of rabbits and foxes, and the characteristics of their interactions. If we ously, we could alterna-
tively create a general rate
were studying a real system, these values would come from observations of real
function M-file which
animals, but for this example I chose values that yield interesting results. takes the parameters as
The last paragraph packs the computed derivatives back into a vector. When inputs, and then define an
ode45 calls this function, it provides a vector as input and expects to get a vector anonymous function in the
as output. Command Window in which
Sharp-eyed readers will notice something different about this line: we pass this information.

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:
358 Chapter 10 Systems of ODEs

120

100

80

Population
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 359

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.


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

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 hope-
fully 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

1
x0 = − 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 function res = example_10_1(t, V)


2 % unpack the elements of V
3 x = V(1);
4 y = V(2);
5
6 % compute the derivatives
7 dxdt = -y;
8 dydt = 1.01*x-0.2*y;
9
10 % pack the derivatives into a vector
11 res = [dxdt; dydt];
12 end
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
0.8 y

0.6

0.4

0.2
x or y

-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
0.8 y
x ref
0.6 y ref

0.4

0.2
x or y

-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

0
y

-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 function res = example_10_2(t, V)


2 % unpack the elements of V
3 x = V(1);
4 y = V(2);
5
6 % compute the derivatives
7 dxdt = 4*x-3*y;
8 dydt = 6*x-7*y;
9
10 % pack the derivatives into a vector
11 res = [dxdt; dydt];
12 end

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

15
x or y

10

-5
0 0.2 0.4 0.6 0.8 1
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')
368 Chapter 10 Systems of ODEs

Solution to Example 10.2


25
x
y
20 x ref
y ref

15

x or y
10

-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

8
y

-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 equa-
tions 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
40 y
z

30

20
x, y, or z

10

-10

-20

-30
0 5 10 15 20 25 30
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:


372 Chapter 10 Systems of ODEs

50

40

30

z 20

10

0
50
20
0 10
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 % function [T,Y] = euler_ode_system(fcn_handle, Trange, dt, IC)
2 % Function to use Euler's method to solve a system of
3 % differential equations.
4 %
5 % Required input:
6 % fcn_handle: The function handle to the function to
7 % compute the rate of change of our
8 % functions. Rates can be returned as
9 % either a column or row vector.
10 % Trange: Time vector where Trange = [t0, tfinal]
11 % dt: the time step
12 % IC: Intial condition vector. If we have functions
13 % X and Y, this is IC = [X0, Y0]. However, it
14 % is designed for an arbitrary number of
15 % of equations. Note that I assume it is a row vector.
16 % The effects my assigment of the initial conditions to
17 % the first row of the Y matrix.
18 %
19 function [T,Y] = euler_ode_system(fcn_handle, Trange, dt, IC)
20 %
21 % Un-packing Trange. The first element is t0 and the second is Tfinal
22 t0 = Trange(1);
23 tfinal = Trange(2);
24
25 % With a fixed timestep, we can create a vector of times at which we
26 % will evaluate our function. I will then take the transpose to get a
27 % column vector like ode45.
28 T = t0:dt:tfinal;
29 T = T';
30
31 % Pre-size our Y matrix. It will be setup like a matrix just like ode45
32 % would use. The number of rows will be the same as the length of T.
33 % The number of columns will be the same as the number of functions,
34 % which is the length of IC
35 Y = zeros(length(T),length(IC));
36 %
37 % Store the initial conditions row 1 of Y.
38 Y(1,:) = IC;
39
40 % Looping over all times. We will start with i=2 since i=1 just
41 % corresponds to our initial conditions. I could start at i=1, but then
42 % need to shift my indices.
43 for i=2:length(T)
44 DY = fcn_handle(T(i-1),Y(i-1,:));
45 % Note I need to transpose DY. It is a column vector as returned by
46 % the rate function. I will add to it the i-1 row of my Y vector,
47 % which is row vector.
48 Y(i,:) = DY'*dt+Y(i-1,:);
49 end
50
51 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 exam-
ple.
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
nB
S B /C =
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 corre-
spond 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 377

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 expres-
sion 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
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 possi-
ble 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
a 2
R 0 = aR − 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 popula-
tion 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 suscepti-
ble 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 Exer-
cise 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
½ ¾
¡ 2 −1
¢ −23700J/mol
r 2 = 1.87 × 10 min exp CB
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.
Chapter 11
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 Listing 11.1 v_virial.m


functions, I recommended
the MATLAB documen- 1 function res = v_virial( t,p_bar,tc,pc_bar,omega )
tation page “Share Data 2 p = p_bar*1e5;
Between Workspaces.” The 3 pc = pc_bar*1e5;
best practice for sharing 4 r = 8.314;
data is by passing argu-
5
ments, which includes the
6 tr = t/tc;
use of anonymous func-
tions. GNU Octave makes 7
a similar recommendation. 8 b0 = 0.083-0.422/(tr^(1.6));
Now that we have exten- 9 b1 = 0.139-0.172/(tr^(4.2));
sive experience working 10 b = r*tc/pc*(b0+omega*b1);
with functions, I introduce 11
nested functions, which 12 z = 1 + b*p/(r*t);
once they understand how
13
they work, many students
find to be a very convenient
14 vig = v_ideal_gas(t,p_bar);
alternative to using anony- 15
mous functions. 16 res = z*vig;
17 end
18
19 function res = v_ideal_gas( t,p_bar )
20 p = p_bar*1e5;
21 r = 8.314;
22 v = r*t/p;
23 res = v*(100^(3));
24 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 function res = v_virial_2( t,p_bar,tc,pc_bar,omega )


2 p = p_bar*1e5;
3 pc = pc_bar*1e5;
4 r = 8.314;
5
6 tr = t/tc;
7
8 b0 = 0.083-0.422/(tr^(1.6));
9 b1 = 0.139-0.172/(tr^(4.2));
10 b = r*tc/pc*(b0+omega*b1);
11
12 z = 1 + b*p/(r*t);
13
14 vig = v_ideal_gas(t,p_bar);
15
16 res = z*vig;
17
18 function res = v_ideal_gas( t,p_bar )
19 p = p_bar*1e5;
20 r = 8.314;
21 v = r*t/p;
22 res = v*(100^(3));
23 end
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 t Note that in the MATLAB
accessed from the inner function, and likewise the variables of the inner function documentation, the outer
function is the parent func-
can be accessed by the outer function. This is not possible with parallel functions.
tion, and the inner function
In this example, using a nested function makes it so that we no longer need to pass (or functions) is the nested
the temperature and pressure to v_ideal_gas, and we additionally no longer function
need to redefine the gas constant.
386 Chapter 11 Second-order systems

Listing 11.3 v_virial_3.m

1 function res = v_virial_3( t,p_bar,tc,pc_bar,omega )


2 p = p_bar*1e5;
3 pc = pc_bar*1e5;
4 r = 8.314;
5
6 tr = t/tc;
7
8 b0 = 0.083-0.422/(tr^(1.6));
9 b1 = 0.139-0.172/(tr^(4.2));
10 b = r*tc/pc*(b0+omega*b1);
11
12 z = 1 + b*p/(r*t);
13
14 vig = v_ideal_gas();
15
16 res = z*vig;
17
18 function res = v_ideal_gas()
19 v = r*t/p;
20 res = v*(100^(3));
21 end
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 be-
havior 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 % res = duck2(x,r,rho)
2 % Function to find the required depth of a duck to remain
3 % buoyant in water. duck2 requires that we pass parameters
4 % r (radius of duck) and rho (density of the liquid), in
5 % addition to the depth (x) which is what we will solve for.
6 function res = duck2( x,r,rho )
7 res = 0.3.*4.*r.^(3)-rho.*(3.*r.*x.^(2)-x.^(3));
8 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. t Remember, an anonymous
To accomplish this, I will create a top function duck_zero to which I will pass r function is restricted to a
single executable statement.
and ρ, and it will return the depth d . I will then nest duck2. In this way, duck2
There is no such restriction
can “see” the value of r and ρ in the top function, so that d is the only variable in a nested function.
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 function res = duck_zero( r, rho )


2 res = fzero(@duck2,[0,r]);
3
4 function res = duck2( x )
5 res = 0.3*4*r^(3)-rho*(3*r*x^(2)-x^(3));
6 end
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 applica-
tion to ode45, which has a restriction of two input variables, later in this chapter.
Application to fsolve is also straightforward.
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

The first equation says that the first derivative of z is v; the second says that the
derivative of v is a.
t Screen casts are available Note that while in this chapter we will focus on examples from Newtonian
for Professor Paluch work- mechanics, the same strategy can be used in general to solve higher order (second
ing through the following
order and higher) ODEs; we can convert the ODE to a system of first order ODEs,
three sections: Freefall, Air
resistance, and Parachute.
which we learned to solve in the previous chapter.
They should be viewed as
a series of screen casts that
build-off of each other.
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 function res = freefall(t, X)


2 % Un-pack
3 z = X(1); % the first element is position
4 v = X(2); % the second element is velocity
5
6 % Compute rates
7 dzdt = v;
8 dvdt = acceleration(t, z, v);
9
10 % Pack the rates up as a column vector
11 res = [dzdt; dvdt];
12 end
13
14 function res = acceleration(t, z, v)
15 g = -9.8; % acceleration of gravity in m/s^2
16 res = g;
17 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

-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, stor-
ing 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:

4000
position [m]

2000

-2000
0 5 10 15 20 25 30

0
velocity [m/s]

-100

-200

-300
0 5 10 15 20 25 30
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 function res = freefall_time(t)


2 % Solve over the range 0 to t
3 [T,M] = ode45(@freefall, [0, t], [4000, 0]);
4 % The first column of M is position, the second is velocity
5 Z = M(:,1);
6 res = Z(end);
7 end
8
9 function res = freefall(t, X)
10 % Un-pack
11 z = X(1); % the first element is position
12 v = X(2); % the second element is velocity
13
14 % Compute rates
15 dzdt = v;
16 dvdt = acceleration(t, z, v);
17
18 % Pack the rates into a column vector
19 res = [dzdt; dvdt];
20 end
21
22 function res = acceleration(t, z, v)
23 g = -9.8; % acceleration of gravity in m/s^2
24 res = g;
25 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 function res = freefall_contact()


2 t_contact = fzero(@freefall_time,[0.1,30]);
3 res = t_contact;
4 end
5
6 function res = freefall_time(t)
7 % Solve over the range 0 to t
8 [T,M] = ode45(@freefall, [0, t], [4000, 0]);
9 % The first column of M is position, the second is velocity
10 Z = M(:,1);
11 res = Z(end);
12 end
13
14 function res = freefall(t, X)
15 % Un-pack
16 z = X(1); % the first element is position
17 v = X(2); % the second element is velocity
18
19 % Compute rates
20 dzdt = v;
21 dvdt = acceleration(t, z, v);
22
23 % Pack the rates into a column vector
24 res = [dzdt; dvdt];
25 end
26
27 function res = acceleration(t, z, v)
28 g = -9.8; % acceleration of gravity in m/s^2
29 res = g;
30 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 function [t_contact, v_contact] = freefall_contact_b()


2 t_contact = fzero(@freefall_time,[0.1,30]);
3 [T,M] = ode45(@freefall, [0, t_contact], [4000, 0]);
4 V = M(:,2);
5 v_contact = V(end);
6 end
7
8 function res = freefall_time(t)
9 [T,M] = ode45(@freefall, [0, t], [4000, 0]);
10 % The first column of M is position, the second is velocity
11 Z = M(:,1);
12 res = Z(end);
13 end
14
15 function res = freefall(t, X)
16 z = X(1); % the first element is position
17 v = X(2); % the second element is velocity
18
19 dzdt = v;
20 dvdt = acceleration(t, z, v);
21
22 res = [dzdt; dvdt]; % pack the results in a column vector
23 end
24
25 function res = acceleration(t, z, v)
26 g = -9.8; % acceleration of gravity in m/s^2
27 res = g;
28 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 func-
tion. 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. Re-
member 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 addi-
tional 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
3 t_contact = fzero(@freefall_time,[0.1,30]);
4 V = M(:,2);
5 v_contact = V(end);
6
7 function res = freefall_time(t)
8 [T,M] = ode45(@freefall, [0, t], [4000, 0]);
9 % The first column of M is position, the second is velocity
10 Z = M(:,1);
11 res = Z(end);
12 end
13
14 end
15
16 function res = freefall(t, X)
17 z = X(1); % the first element is position
18 v = X(2); % the second element is velocity
19
20 dzdt = v;
21 dvdt = acceleration(t, z, v);
22
23 res = [dzdt; dvdt]; % pack the results in a column vector
24 end
25
26 function res = acceleration(t, z, v)
27 g = -9.8; % acceleration of gravity in m/s^2
28 res = g;
29 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 fol-
lowing 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 up-
graded 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 function [t_contact,v_contact] = freefall_contact_d()


2 % We can use empty brackets to define a variable without assigning it a
3 % value. This corresponds to an empty matrix.
4 M = [];
5 t_contact = fzero(@freefall_time,[0.1,30]);
6 V = M(:,2);
7 v_contact = V(end);
8
9 function res = freefall_time(t)
10 [T,M] = ode45(@freefall, [0, t], [4000, 0]);
11 % The first column of M is position, the second is velocity
12 Z = M(:,1);
13 res = Z(end);
14 end
15
16 end
17
18 function res = freefall(t, X)
19 z = X(1); % the first element is position
20 v = X(2); % the second element is velocity
21
22 dzdt = v;
23 dvdt = acceleration(t, z, v);
24
25 res = [dzdt; dvdt]; % pack the results in a column vector
26 end
27
28 function res = acceleration(t, z, v)
29 g = -9.8; % acceleration of gravity in m/s^2
30 res = g;
31 end
11.4 Air resistance 397

I created a variable without assigning a value using empty brackets; this is equiva-
lent 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
for Professor Paluch work-
11.4 Air resistance ing through the following
three sections: Freefall, Air
To make this simulation more realistic, we can add air resistance (or drag). For resistance, and Parachute.
large objects moving quickly through air, the force due to air resistance, called They should be viewed as
“drag,” is proportional to v 2 : 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 t In your fluid mechanics
acceleration can then be computed using Newton’s second law: a drag = F drag /m. class you likely saw the drag
equation: F drag = 12 ρv 2C D A,
Here’s a version of acceleration that takes air resistance into account (you
where ρ is the density of the
don’t have to make any changes in freefall, I just renamed it freefall_2 so as fluid (here air), C D is the
not to be confused with the case of no drag). Recall that while gravity is pulling drag coefficient which is a
the skydiver down, drag acts in the opposite direction of motion; in this case function of Reynold’s num-
where the skydiver is falling down, drag is pushing the skydiver up. ber, and A is the reference
cross-sectional area (here
the cross-sectional area
Listing 11.12 freefall_2.m
of the skydiver). We lump
1 function res = freefall_2(t, X) these parameters into a
2 z = X(1); % the first element is position single parameter (drag con-
3 v = X(2); % the second element is velocity stant), c for convenience.
4
5 dzdt = v;
6 dvdt = acceleration(t, z, v);
7
8 res = [dzdt; dvdt]; % pack the results in a column vector
9 end
10 function res = acceleration(t, z, v)
11 a_grav = -9.8; % acceleration of gravity in m/s^2
12 c = 0.2; % drag constant
13 m = 75; % mass in kg
14 f_drag = c * v^2; % drag force in N
15 a_drag = f_drag / m; % drag acceleration in m/s^2
16 res = a_grav + a_drag; % total acceleration
17 end
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

-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:

4000

3500
position [m]

3000

2500

2000
0 5 10 15 20 25 30

-20
velocity [m/s]

-40

-60

-80
0 5 10 15 20 25 30
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 function [t_contact,v_contact] = freefall_2_contact()


2 M = []; % Creating variable without assigning a value.
3 t_contact = fzero(@freefall_time,[0.1,90]);
4 V = M(:,2);
5 v_contact = V(end);
6
7 function res = freefall_time(t)
8 [T,M] = ode45(@freefall, [0, t], [4000, 0]);
9 % The first column of M is position, the second is velocity
10 Z = M(:,1);
11 res = Z(end);
12 end
13
14 end
15
16 function res = freefall(t, X)
17 z = X(1); % the first element is position
18 v = X(2); % the second element is velocity
19
20 dzdt = v;
21 dvdt = acceleration(t, z, v);
22
23 res = [dzdt; dvdt]; % pack the results in a column vector
24 end
25 function res = acceleration(t, z, v)
26 a_grav = -9.8; % acceleration of gravity in m/s^2
27 c = 0.2; % drag constant
28 m = 75; % mass in kg
29 f_drag = c * v^2; % drag force in N
30 a_drag = f_drag / m; % drag acceleration in m/s^2
31 res = a_grav + a_drag; % total acceleration
32 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 function [t_contact,v_contact] = freefall_3_contact()
2 M = []; T=[]; Z=[]; % Creating variables without assigning a value.
3 t_contact = fzero(@freefall_time,[0.1,90]);
4 V = M(:,2);
5 v_contact = V(end);
6
7 % Now create the stacked plot too. Here the final
8 % time will be the contact time.
9 figure
10 subplot(2,1,1) % First plot in 2 by 1 matrix
11 plot(T,Z,'-r')
12 ylabel('position [m]')
13 subplot(2,1,2) % Second plot in 2 by 1 matrix
14 plot(T,V,'-b')
15 ylabel('velocity [m/s]')
16 xlabel('time [s]')
17 print('-depsc','freefall_stack_3.eps')
18
19 function res = freefall_time(t)
20 [T,M] = ode45(@freefall, [0, t], [4000, 0]);
21 % The first column of M is position, the second is velocity
22 Z = M(:,1);
23 res = Z(end);
24 end
25
26 end
27
28 function res = freefall(t, X)
29 z = X(1); % the first element is position
30 v = X(2); % the second element is velocity
31
32 dzdt = v;
33 dvdt = acceleration(t, z, v);
34
35 res = [dzdt; dvdt]; % pack the results in a column vector
36 end
402 Chapter 11 Second-order systems

37 function res = acceleration(t, z, v)


38 a_grav = -9.8; % acceleration of gravity in m/s^2
39 c = 0.2; % drag constant
40 m = 150; % mass in kg
41 f_drag = c * v^2; % drag force in N
42 a_drag = f_drag / m; % drag acceleration in m/s^2
43 res = a_grav + a_drag; % total acceleration
44 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

3000
position [m]

2000

1000

0
0 10 20 30 40 50 60

-20
velocity [m/s]

-40

-60

-80

-100
0 10 20 30 40 50 60
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 Win-
dow? 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 ver-
sion 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 function [t_contact,v_contact] = freefall_3m_contact(m,fname)
2 M = []; T=[]; Z=[]; % Creating variables without assigning a value.
3 t_contact = fzero(@freefall_time,[0.1,90]);
4 V = M(:,2);
5 v_contact = V(end);
6
7 % Now create the stacked plot too. Here the final
8 % time will be the contact time.
9 figure
10 subplot(2,1,1) % First plot in 2 by 1 matrix
11 plot(T,Z,'-r')
12 ylabel('position [m]')
13 subplot(2,1,2) % Second plot in 2 by 1 matrix
14 plot(T,V,'-b')
15 ylabel('velocity [m/s]')
16 xlabel('time [s]')
17 print('-depsc',fname)
18
19 function res = freefall_time(t)
20 [T,M] = ode45(@freefall, [0, t], [4000, 0]);
21 % The first column of M is position, the second is velocity
22 Z = M(:,1);
23 res = Z(end);
24 end
25
26 function res = freefall(t, X)
27 z = X(1); % the first element is position
28 v = X(2); % the second element is velocity
29
30 dzdt = v;
31 dvdt = acceleration(t, z, v);
32
33 res = [dzdt; dvdt]; % pack the results in a column vector
34 end
35
36 function res = acceleration(t, z, v)
37 a_grav = -9.8; % acceleration of gravity in m/s^2
38 c = 0.2; % drag constant
39 f_drag = c * v^2; % drag force in N
40 a_drag = f_drag / m; % drag acceleration in m/s^2
41 res = a_grav + a_drag; % total acceleration
42 end
43
404 Chapter 11 Second-order systems

44 end

Solving:

>> >> [t_sol,v_sol] = freefall_3m_contact(75,'freefall_3_75m.eps')


t_sol = 70.2693
v_sol = -60.6218

4000

3000
position [m]

2000

1000

-1000
0 10 20 30 40 50 60 70 80

0
velocity [m/s]

-20

-40

-60
0 10 20 30 40 50 60 70 80
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

3000
position [m]

2000

1000

0
0 10 20 30 40 50 60

-20
velocity [m/s]

-40

-60

-80

-100
0 10 20 30 40 50 60
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 t Screen casts are available
about 60 m/s, which is about 130 mph. If you hit the ground at that speed, you for Professor Paluch work-
ing through the following
would almost certainly be killed. That’s where parachutes come in.
three sections: Freefall, Air
resistance, and Parachute.
They should be viewed as
Example 11.2
a series of screen casts that
Modify acceleration so that after 30 seconds of free-fall the skydiver deploys a build-off of each other.
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.
406 Chapter 11 Second-order systems

Listing 11.16 parachute_contact.m


1 function [t_contact,v_contact] = parachute_contact()
2 M = []; T=[]; Z=[]; % Creating variables without assigning a value.
3 t_contact = fzero(@freefall_time,[0.1,300]);
4 V = M(:,2);
5 v_contact = V(end);
6
7 % Now create the stacked plot too. Here the final
8 % time will be the contact time.
9 figure
10 subplot(2,1,1) % First plot in 2 by 1 matrix
11 plot(T,Z,'-r')
12 ylabel('position [m]')
13 subplot(2,1,2) % Second plot in 2 by 1 matrix
14 plot(T,V,'-b')
15 ylabel('velocity [m/s]')
16 xlabel('time [s]')
17 print('-depsc','parachute.eps')
18
19 function res = freefall_time(t)
20 [T,M] = ode45(@freefall, [0, t], [4000, 0]);
21 % The first column of M is position, the second is velocity
22 Z = M(:,1);
23 res = Z(end);
24 end
25
26 end
27
28 function res = freefall(t, X)
29 z = X(1); % the first element is position
30 v = X(2); % the second element is velocity
31
32 dzdt = v;
33 dvdt = acceleration(t, z, v);
34
35 res = [dzdt; dvdt]; % pack the results in a column vector
36 end
37 function res = acceleration(t, z, v)
38 a_grav = -9.8; % acceleration of gravity in m/s^2
39 c = 0.2; % drag constant
40 % After 30 seconds the parachute is deployed
41 if t > 30
42 c = 2.7;
43 end
44 m = 75; % mass in kg
45 f_drag = c * v^2; % drag force in N
46 a_drag = f_drag / m; % drag acceleration in m/s^2
47 res = a_grav + a_drag; % total acceleration
48 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

3000
position [m]

2000

1000

-1000
0 20 40 60 80 100 120 140 160 180

0
velocity [m/s]

-20

-40

-60
0 20 40 60 80 100 120 140 160 180
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 function res = parachute_2(tfinal)
2 t0 = 0; % initial time
3 t1 = 30;% end of first integration range
4 z0 = 4000; % initial altitude
5 v0 = 0; % initial velocity
6 flag = 0; % use c = 0.2;
7 [T, M] = ode45(@freefall, [t0, t1], [z0, v0]);
8 v1 = M(end,2); % velocity after 30 s
9 z1 = M(end,1); % altitude after 30 s
10 flag = 1; % use c = 2.7
11 [T, M] = ode45(@freefall, [t1, tfinal], [z1, v1]);
12 res = M(end,1); % altitude at time t
13 % Uncomment when you want to call parachute
14 % and display the velocity.
15 %M(end,2)
16
17 function res = freefall(t, X)
18 z = X(1); % the first element is position
19 v = X(2); % the second element is velocity
20
21 dzdt = v;
22 dvdt = acceleration(t,z,v);
23
24 res = [dzdt; dvdt]; % pack the results in a column vector
25 end
26
27 function res = acceleration(t,z,v)
28 a_grav = -9.8; % acceleration of gravity in m/s^2
29 if flag == 0 % solving from 0 to 30 s
30 c = 0.2;
31 else
32 c = 2.7; % solving from 30 s to tfinal
33 end
34 m = 75; % mass in kg
35 f_drag = c * v^2; % drag force in N
36 a_drag = f_drag / m; % drag acceleration in m/s^2
37 res = a_grav + a_drag; % total acceleration
38 end
39
40 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 sin-
gle 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 function res = projectile(t, W)


2 P = W(1:2);
3 V = W(3:4);
4
5 dPdt = V;
6 dVdt = acceleration(t, P, V);
7
8 res = [dPdt; dVdt];
9 end
10
11 function res = acceleration(t, P, V)
12 g = 9.8; % acceleration of gravity in m/s^2
13 res = [0; -g];
14 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 function res = projectile_a(t, W)


2 % Position, x then y
3 px = W(1);
4 py = W(2);
5 % Velocity, x then y
6 vx = W(3);
7 vy = W(4);
8
9 % Computing rates
10 dpxdt = vx;
11 dpydt = vy;
12 dvxdt = acceleration_x(t,px,vx);
13 dvydt = acceleration_y(t,py,vy);
14
15 % Packing up the rates
16 res = [dpxdt; dpydt; dvxdt; dvydt];
17 end
18
19 function res = acceleration_x(t, px, vx)
20 res = 0;
21 end
22
23 function res = acceleration_y(t, py, vy)
24 g = 9.8; % acceleration of gravity in m/s^2
25 res = -g;
26 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

-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
3 % The initial position in m
4 x0 = 0;
5 y0 = 3;
6
7 % The initial velocity in m/s and launch angle in degrees
8 v0 = 60;
9 theta0 = 45;
10
11 % Computing the x and y component of the initial velocity
12 vx0 = v0*cosd(theta0);
13 vy0 = v0*sind(theta0);
14
15 % The time range to solve over in s
16 t0 = 0;
17 tfinal = 10;
18
19 % Solving our ODE
20 [T,M] = ode45(@projectile,[t0,tfinal],[x0,y0,vx0,vy0]);
21
22 % Let's break M up. The columns will correspond to our functions in the
23 % order x,y,vx,vy, the same order used by the initial conditions and
24 % within the rate function.
25 X = M(:,1);
26 Y = M(:,2);
27 VX = M(:,3);
28 VY = M(:,4);
29
30 % Next, let's plot the trajectory in the xy plane
31 figure(1)
32 plot(X,Y,'-k')
33 xlabel('x position [m]')
34 ylabel('y position [m]')
35 title('Trajectory in xy plan')
36 print('-depsc','xy_trajectory.eps')
37
38 end
39
40 function res = projectile(t, W)
41 P = W(1:2);
42 V = W(3:4);
43
11.6 Two dimensions 413

44 dPdt = V;
45 dVdt = acceleration(t, P, V);
46
47 res = [dPdt; dVdt];
48 end
49
50 function res = acceleration(t, P, V)
51 g = 9.8; % acceleration of gravity in m/s^2
52 res = [0; -g];
53 end

>> [T,X,Y,VX,VY] = projectile_model();

Trajectory in xy plan
100

80

60

40
y position [m]

20

-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 % The initial position in m
4 x0 = 0;
5 y0 = 3;
6
7 % The initial velocity in m/s and launch angle in degrees
8 v0 = 60;
9 theta0 = 45;
10
11 % Computing the x and y component of the initial velocity
12 vx0 = v0*cosd(theta0);
13 vy0 = v0*sind(theta0);
14
15 % The initial time. We will solve for the final time as the time
16 % required to make contact, y=0. (This criteria can also be updated.)
17 t0 = 0;
18
19 % Solving for the time required to make contact using fzero. The error
20 % function will be nested. The final time fzero calls the error
21 % function will correspond to when t is when contact is made. By
22 % nesting we will not need to resolve at this time. The error function
23 % is just solving the ODEs over the rante t0 to t, and returning the y
24 % position at t.
25 M = []; % Creating variable M without assign a value.
26 t_impact = fzero(@projectile_solve,[0.1,100]);
27
28 % Let's break M up. The columns will correspond to our functions in the
29 % order x,y,vx,vy, the same order used by the initial conditions and
30 % within the rate function.
11.6 Two dimensions 415

31 X = M(:,1);
32 Y = M(:,2);
33 VX = M(:,3);
34 VY = M(:,4);
35
36 % Next, let's plot the trajectory in the xy plane
37 figure(1)
38 plot(X,Y,'-k')
39 xlabel('x position [m]')
40 ylabel('y position [m]')
41 title('Trajectory in xy plan')
42 print('-depsc','xy_trajectory.eps')
43
44 function res = projectile_solve(t)
45 % Solving our ODE over the range t0 to t
46 [T,M] = ode45(@projectile,[t0,t],[x0,y0,vx0,vy0]);
47
48 % Returning the y position at t
49 res = M(end,2);
50 end
51
52 end
53
54 function res = projectile(t, W)
55 P = W(1:2);
56 V = W(3:4);
57
58 dPdt = V;
59 dVdt = acceleration(t, P, V);
60
61 res = [dPdt; dVdt];
62 end
63
64 function res = acceleration(t, P, V)
65 g = 9.8; % acceleration of gravity in m/s^2
66 res = [0; -g];
67 end

>> [T,X,Y,VX,VY] = projectile_model_2();


416 Chapter 11 Second-order systems

Trajectory in xy plan
100

90

80

70

60
y position [m]
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 417

Listing 11.22 projectile_model_angle.m

1 function [Theta0,X_impact] = projectile_model_angle()


2
3 % The initial position in m
4 x0 = 0;
5 y0 = 3;
6
7 % The initial velocity in m/s
8 v0 = 60;
9
10 % Vector of initial launch angles in degrees
11 Theta0 = 30:1:60;
12
13 % Computing the x and y component of the initial velocity
14 Vx0 = v0*cosd(Theta0);
15 Vy0 = v0*sind(Theta0);
16
17 % The initial time. We will solve for the final time as the time
18 % required to make contact, y=0. (This criteria can also be updated.)
19 t0 = 0;
20
21 % Creating a vector to store the distance travled to
22 X_impact = zeros(1,length(Theta0));
23
24 % Looping over initial launch angles and solving for the distance
25 % traveled. This will be collected in a matrix.
26 M = []; % Creating variables without assiging a value
27 for i = 1:length(Theta0)
28 t_impact = fzero(@projectile_solve,[0.1,100]);
29 % Collect the x distance travled at impact
30 X_impact(i) = M(end,1);
31 end
32
33 % Generate a plot of distance travled vs launch angle
34 plot(Theta0,X_impact,'-ok')
35 xlabel('Launch angle [degrees]')
36 ylabel('Distance traveled [m]')
37 print('-depsc','optimal_launch_angle.eps')
38
39 function res = projectile_solve(t)
40 % Solving our ODE over the range t0 to t
41 [T,M] = ode45(@projectile,[t0,t],[x0,y0,Vx0(i),Vy0(i)]);
42
43 % Returning the y position at t
418 Chapter 11 Second-order systems

44 res = M(end,2);
45 end
46
47 end
48
49 function res = projectile(t, W)
50 P = W(1:2);
51 V = W(3:4);
52
53 dPdt = V;
54 dVdt = acceleration(t, P, V);
55
56 res = [dPdt; dVdt];
57 end
58
59 function res = acceleration(t, P, V)
60 g = 9.8; % acceleration of gravity in m/s^2
61 res = [0; -g];
62 end

>> [Theta0,X_impact] = projectile_model_angel();

380

370

360
Distance traveled [m]

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 % The initial position in m
4 x0 = 0;
5 y0 = 3;
6
7 % The initial velocity in m/s and launch angle in degrees
8 v0 = 60;
9 theta0 = 45;
10
11 % Computing the x and y component of the initial velocity
12 vx0 = v0*cosd(theta0);
13 vy0 = v0*sind(theta0);
14
15 % The initial time. We will solve for the final time as the time
16 % required to make contact, y=0. (This criteria can also be updated.)
17 t0 = 0;
18
19 % Solving for the time required to make contact using fzero. The error
20 % function will be nested. The final time fzero calls the error
21 % function will correspond to when t is when contact is made. By
22 % nesting we will not need to resolve at this time. The error function
23 % is just solving the ODEs over the rante t0 to t, and returning the y
24 % position at t.
25 M = []; % Creating variable M without assign a value.
26 t_impact = fzero(@projectile_solve,[0.1,100]);
27
28 % Let's extract X and Y from M to facilitate plotting. We will also
29 % return the distance traveled.
30 X = M(:,1);
31 Y = M(:,2);
32 %VX = M(:,3);
33 %VY = M(:,4);
34 res = X(end);
35
36 % Next, let's plot the trajectory in the xy plane
37 figure(1)
38 plot(X,Y,'-k')
39 xlabel('x position [m]')
40 ylabel('y position [m]')
41 title('Trajectory in xy plan')
42 print('-depsc','xy_trajectory.eps')
43
11.6 Two dimensions 421

44 function res = projectile_solve(t)


45 % Solving our ODE over the range t0 to t
46 [T,M] = ode45(@projectile,[t0,t],[x0,y0,vx0,vy0]);
47
48 % Returning the y position at t
49 res = M(end,2);
50 end
51
52 end
53
54 function res = projectile(t, W)
55 P = W(1:2);
56 V = W(3:4);
57
58 dPdt = V;
59 dVdt = acceleration(t, P, V);
60
61 res = [dPdt; dVdt];
62 end
63
64 function res = acceleration(t, P, V)
65 g = 9.8; % acceleration of gravity in m/s^2
66 %
67 c = 0.1; % drag constant
68 m = 75; % mass in kg
69 Vhat = sign(V); % unit vector giving direction of velocity
70 % componetns
71 f_drag = -c * V.^(2) .* Vhat; % drag force in N
72 a_drag = f_drag ./ m; % drag acceleration in m/s^2
73
74 % Return as vector acceleration in x and y direction.
75 % In the x direction is just drag. In the y direction it is drag - g.
76 res = [a_drag(1); a_drag(2)-g];
77
78 end

>> x_dist = projectile_resistance()


x_dist = 287.7751
422 Chapter 11 Second-order systems

Trajectory in xy plan
90

80

70

60

50
y position [m]
40

30

20

10

-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 423

Listing 11.24 projectile_resistance_2.m

1 function res = projectile_resistance_2(v0)


2
3 % The initial position in m
4 x0 = 0;
5 y0 = 3;
6
7 % The initial velocity in m/s and launch angle in degrees
8 %v0 = 60;
9 theta0 = 45;
10
11 % Computing the x and y component of the initial velocity
12 vx0 = v0*cosd(theta0);
13 vy0 = v0*sind(theta0);
14
15 % The initial time. We will solve for the final time as the time
16 % required to make contact, y=0. (This criteria can also be updated.)
17 t0 = 0;
18
19 % Solving for the time required to make contact using fzero. The error
20 % function will be nested. The final time fzero calls the error
21 % function will correspond to when t is when contact is made. By
22 % nesting we will not need to resolve at this time. The error function
23 % is just solving the ODEs over the rante t0 to t, and returning the y
24 % position at t.
25 M = []; % Creating variable M without assign a value.
26 t_impact = fzero(@projectile_solve,[0.1,100]);
27
28 % Let's extract X and Y from M to facilitate plotting. We will also
29 % return the distance traveled.
30 X = M(:,1);
31 Y = M(:,2);
32 %VX = M(:,3);
33 %VY = M(:,4);
34 res = X(end);
35
36 % Next, let's plot the trajectory in the xy plane
37 figure(1)
38 plot(X,Y,'-k')
39 xlabel('x position [m]')
40 ylabel('y position [m]')
41 title('Trajectory in xy plan')
42 print('-depsc','xy_trajectory.eps')
43
424 Chapter 11 Second-order systems

44 function res = projectile_solve(t)


45 % Solving our ODE over the range t0 to t
46 [T,M] = ode45(@projectile,[t0,t],[x0,y0,vx0,vy0]);
47
48 % Returning the y position at t
49 res = M(end,2);
50 end
51
52 end
53
54 function res = projectile(t, W)
55 P = W(1:2);
56 V = W(3:4);
57
58 dPdt = V;
59 dVdt = acceleration(t, P, V);
60
61 res = [dPdt; dVdt];
62 end
63
64 function res = acceleration(t, P, V)
65 g = 9.8; % acceleration of gravity in m/s^2
66 %
67 c = 0.1; % drag constant
68 m = 75; % mass in kg
69 Vhat = sign(V); % unit vector giving direction of velocity
70 % componetns
71 f_drag = -c * V.^(2) .* Vhat; % drag force in N
72 a_drag = f_drag ./ m; % drag acceleration in m/s^2
73
74 % Return as vector acceleration in x and y direction.
75 % In the x direction is just drag. In the y direction it is drag - g.
76 res = [a_drag(1); a_drag(2)-g];
77
78 end

I could keep updating the code, but it is also easy enough to solve in the Com-
mand 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 425

notice that each time fzero calls projectile_model_resistance_2 it gener-


ates 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 % Creating variables without assiging an initial value
4 X = [];
5 Y = [];
6
7 res = fzero(@projectile_error,[0.1,60]);
8
9 % Next, let's plot the trajectory in the xy plane
10 figure(1)
11 plot(X,Y,'-k')
12 xlabel('x position [m]')
13 ylabel('y position [m]')
14 title('Trajectory in xy plan')
15 print('-depsc','xy_trajectory.eps')
16
17 function res = projectile_error(v0)
18
19 % The initial position in m
20 x0 = 0;
21 y0 = 3;
22
23 % The initial velocity in m/s and launch angle in degrees
24 %v0 = 60;
25 theta0 = 45;
26
27 % Computing the x and y component of the initial velocity
28 vx0 = v0*cosd(theta0);
29 vy0 = v0*sind(theta0);
30
31 % The initial time. We will solve for the final time as the time
32 % required to make contact, y=0. (This criteria can also be updated.)
33 t0 = 0;
34
35 % Solving for the time required to make contact using fzero. The error
36 % function will be nested. The final time fzero calls the error
37 % function will correspond to when t is when contact is made. By
38 % nesting we will not need to resolve at this time. The error function
39 % is just solving the ODEs over the rante t0 to t, and returning the y
40 % position at t.
426 Chapter 11 Second-order systems

41 M = []; % Creating variable M without assign a value.


42 t_impact = fzero(@projectile_solve,[0.1,100]);
43
44 % Let's extract X and Y from M to facilitate plotting.
45 X = M(:,1);
46 Y = M(:,2);
47 VX = M(:,3);
48 VY = M(:,4);
49
50 % return the distance traveled - 61.06 m, the world record
51 res = X(end) - 61.06;
52
53 function res = projectile_solve(t)
54 % Solving our ODE over the range t0 to t
55 [T,M] = ode45(@projectile,[t0,t],[x0,y0,vx0,vy0]);
56
57 % Returning the y position at t
58 res = M(end,2);
59 end
60
61 end
62
63 end
64
65 function res = projectile(t, W)
66 P = W(1:2);
67 V = W(3:4);
68
69 dPdt = V;
70 dVdt = acceleration(t, P, V);
71
72 res = [dPdt; dVdt];
73 end
74
75 function res = acceleration(t, P, V)
76 g = 9.8; % acceleration of gravity in m/s^2
77 %
78 c = 0.1; % drag constant
79 m = 75; % mass in kg
80 Vhat = sign(V); % unit vector giving direction of velocity
81 % componetns
82 f_drag = -c * V.^(2) .* Vhat; % drag force in N
83 a_drag = f_drag ./ m; % drag acceleration in m/s^2
84
11.7 What could go wrong? 427

85 % Return as vector acceleration in x and y direction.


86 % In the x direction is just drag. In the y direction it is drag - g.
87 res = [a_drag(1); a_drag(2)-g];
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 0
0 0 0
1 1 1
1 1 1

These operations only work if the matrices are the same size along the dimen-
sion 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 func-
tion 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 function res = projectile(t, W)


2 P = W(1:2);
3 V = W(3:4);
4
5 dPdt = V;
6 dVdt = acceleration(t, P, V);
7
8 res = [dPdt; dVdt];
9 end

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 429

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 func-
tion 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:
430 Chapter 11 Second-order systems

Listing 11.26 event_function.m


1 % When value is equal to zero, an event is triggered.
2 %
3 % Set isterminal to 1 to stop the solver at the first event,
4 % or 0 to get all events.
5 %
6 % direction = 0 if all zeros are to be computed (the default),
7 % +1 if only zeros where the event function is increasing,
8 % and -1 if only zeros where the event function is decreasing.
9 %
10 function [value,isterminal,direction] = event_function(t,W)
11 py = W(2); % Extract the current height.
12 value = py; % When value = 0, an event is triggered
13 isterminal = 1; % Stop the integration if height crosses zero.
14 % (Terminate after first event.)
15 direction = -1; % But only if the height is decreasing.
16 end

t Remember, you do not need t is the time, and W is a vector of the value of our functions at t. For
to unpack W at all. It is a
projectile, this is the position in the x and y direction, and the velocity
habit I tend to do to elim-
in the x and y direction, respectively. Here I unpack just the height since
inate any confusion. You
could just as well use value we don’t use the others.
= W(2). events returns three output variables:

a) value determines when an event occurs. In this case value gets the sec-
ond 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 increas-
ing (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

-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 sev-
eral 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', 1e-

For 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 % When value is equal to zero, an event is triggered.
2 %
3 % Set isterminal to 1 to stop the solver at the first event,
4 % or 0 to get all events.
5 %
6 % direction = 0 if all zeros are to be computed (the default),
7 % +1 if only zeros where the event function is increasing,
8 % and -1 if only zeros where the event function is decreasing.
9 %
10 function [value,isterminal,direction] = event_function_y3(t,W)
11 py = W(2); % Exctract the current height.
12 value = py-3; % Desire to know when height is 3 m.
13 % When value = 0, an event is triggered
14 isterminal = 1; % Stop the integration if height crosses zero.
15 % (Terminate after first event.)
16 direction = -1; % But only if the height is decreasing.
17 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 % When value is equal to zero, and event is triggered.
2 %
3 % Set isterminal to 1 to stop the solver at the first event,
4 % or 0 to get all events.
5 %
6 % direction = 0 if all zeros are to be computed (the default),
7 % +1 if only zeros where the event function is increasing,
8 % and -1 if only zeros where the event function is decreasing.
9 %
10 function [value,isterminal,direction] = event_function_y30(t,W)
11 py = W(2); % Extract the current height.
12 value = [py-3,py]; % Event happens when the height is 3 m and 0 m.
13 isterminal = [0,1]; % Continue after the first event, stop after the second.
14 direction = [-1,-1]; % But only if the height is decreasing.
15 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 3.0000 40.0000 -30.0000
248.8347 0 40.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

0
0 50 100 150 200 250
x [m]

Figure 11.15 ode45(@projectile,[0,10],[0,3,40,30],options)


11.8 ODE Events 435

Example 11.5
Finding the maximum in the trajectory. Find the time when the height of the pro-
jectile 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 % When value is equal to zero, and event is triggered.
2 %
3 % Set isterminal to 1 to stop the solver at the first event,
4 % or 0 to get all events.
5 %
6 % direction = 0 if all zeros are to be computed (the default),
7 % +1 if only zeros where the event function is increasing,
8 % and -1 if only zeros where the event function is decreasing.
9 %
10 function [value,isterminal,direction] = event_function_max(t,W)
11 py = W(2); % Extract the current height
12 vy = W(4); % Extract the current y velocity
13 value = [vy,py]; % Event happens when the y velocity and height are zero
14 isterminal = [0,1]; % Continue after the first event, stop after the second.
15 direction = [-1,-1]; % But only if the velocity and height are decreasing.
16 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 48.9184 40.0000 -0.0000
248.8347 0 40.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

30

y [m] 25

20

15

10

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 % When value is equal to zero, and event is triggered.
2 %
3 % Set isterminal to 1 to stop the solver at the first event,
4 % or 0 to get all events.
5 %
6 % direction = 0 if all zeros are to be computed (the default),
7 % +1 if only zeros where the event function is increasing,
8 % and -1 if only zeros where the event function is decreasing.
9 %
10 function [value,isterminal,direction] = event_function_lotka(t,X)
11 % Find the rate of change of r and f from the rate function,
12 % and unpack the result
13 rate = lotka(t,X);
14 drdt = rate(1);
15 dfdt = rate(2);
16 % rabbit max and min, followed by fox max then min
17 % For all events we continue the integration. For this problem
18 % we know the stopping time is 365 days.
19 value = [drdt,drdt,dfdt,dfdt];
20 isterminal = [0,0,0,0];
21 direction = [-1,1,-1,1];
22 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.

>> options = odeset('Events', @event_function_lotka);


>> [T,M,TE,VE,IE] = ode45(@lotka, [0,365], [100,10], options);
>> IE
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 439

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 sepa-
rating 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')
440 Chapter 11 Second-order systems

120

100

80

population
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 441

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:
442 Chapter 11 Second-order systems

>> options = odeset('Events',@event_function,'RelTol',1e-8,'AbsTol', 1e-8)};


Chapter 12
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 inde-
pendent 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 function res = rats(t, y)


2 a = 0.01;
3 omega = 2 * pi / 365;
4 res = a * y * (1 + sin(omega * t));
5 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, inter-
polation. Interpolating is a broadly applicable technique, not just limited
to differential equations.

12.2 Interpolation
t Here we will use interp1 A better option is to draw a straight line between the two points that bracket Day
for interpolating in 1-D. 180 and use the line to estimate the value in between. This process is called linear
interp2, interp3, and
interpn also exist to inter-
polate in 2-D, 3-D, and N-D,
respectfully.
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 interpo-
lation 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.
t Note that if you use
>> pop = interp1(T, Y, 180, 'spline') ’cubic’ you will get a warn-
ing that the name will be
pop = 38.6486
discontinued in future re-
leases of MATLAB. MAT-
>> pop = interp1(T, Y, 180, 'pchip') LAB instead wants you to
pop = 38.6491 use the keyword ’pchip’,
which they say does the
In this case we expect the spline and cubic interpolations to be better than linear, same thing.
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 t Note that MATLAB has
other way around. Fortunately, they are not very different. built-in functions spline
We can also use interp1 to project the rat population out beyond the values and pchip for interpolating
in T: with these specific meth-
ods. I will exclusively use
interp1 as it is more gen-
>> [T(end), Y(end)] eral an gives us greater
ans = 365.0000 76.9530 flexibility.

>> 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.


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 extrapo-
late 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 va-
por/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) x1 y1
33.428 0.04700 0.14440
36.666 0.09630 0.25740
43.230 0.22070 0.44170
46.450 0.29360 0.52040
50.647 0.40110 0.61390
53.293 0.47590 0.66970
57.722 0.61250 0.76140
60.527 0.70450 0.82010
63.380 0.80810 0.88050
66.037 0.90840 0.94180
67.189 0.95290 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 agree-
ment. 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

0.6
p [kPa]

0.5

0.4

0.3

0.2

0.1

0
30 35 40 45 50 55 60 65 70
x 1 or y 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 We have used interp1 to find population as a function of time; by reversing the
fzero, although you do roles of T and Y, we can also interpolate time as a function of population. For
need to be careful about the example, we might want to know how long it takes the population to reach 20
accuracy... if it’s important. rats.
And there are issues with
multi-valued functions, as
we will see later. We could
>> interp1(Y, T, 20)
alternatively use interp1 ans = 133.4128
to create a function that will
allow us to efficiently use
fzero.
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

60
Y

40

20

0
0 50 100 150 200 250 300 350 400
T
400

300

200
T

100

0
0 10 20 30 40 50 60 70 80
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

1 function res = rats_winter(t, y)


2 a = 0.01;
3 omega = 2 * pi / 365;
4 res = a * y * (0.5 + sin(omega * t));
5 end

Solving and plotting:


>> [T, Y] = ode45(@rats_winter, [0, 365], 2);
>> plot(T,Y)
>> xlabel('T')
>> ylabel('Y')

18

16

14

12
Y

10

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 popula-
tion 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.
t From the documentation
for interp1, linear re-
12.4 Creating a function quires atleast 2 points, and
pchip and spline require
In your code you may find it useful or desirable to have a function that interpo- alteast 4 points.
lates 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 limita-


tions 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
452 Chapter 12 Interpolation

>> fzero(yt,260)
ans =
260.4365

-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 b (t )
0 0.0070
1 0.0036
2 0.0011
3 0.0001
4 0.0004
5 0.0013
6 0.0028
7 0.0043
8 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 function res = interpolate_b(t)


2 T = 0:8;
3 B = [70 36 11 1 4 13 28 43 56] * 1e-4;
4 % While linear is default, and by default MATLAB will not
5 % extrapolate with linear interpolation, I explicitly specify it
6 % here. This way if I change interp1 to use spline or cubic
7 % interpolation, I need just update the method.
8 res = interp1(T, B, t,'linear',NaN);
9 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 function res = interpolate_b_spline(t)
2 T = 0:8;
3 B = [70 36 11 1 4 13 28 43 56] * 1e-4;
4 % While linear is default, and by default MATLAB will not
5 % extrapolate with linear interpolation, I explicitly specify it
6 % here. This way if I change interp1 to use spline or cubic
7 % interpolation, I need just update the method.
8 res = interp1(T, B, t,'spline',NaN);
9 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 interpo-
lation 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 % function res = field_mice(TSPAN,y0,a,flag)
2 %
3 % TSPAN is a two element row vector with elements t0 and tfinal
4 % y0 is the initial population
5 % a is the population parameter
6 % flag will indicate to use linear or spline interpolation
7 % if flag == 1, linear
8 % else, spline
9 %
10 % The function will return a matrix where column 1 is
11 % time and column 2 is the population.
12 %
13 % I will also generate a function of y vs t within the function.
14 % If we are using linear interpolation, the line will be red,
15 % and will be blue for spline.
16 %
17 function res = field_mice(TSPAN,y0,a,flag)
18 [T,Y] = ode45(@field_mice_rate,TSPAN,y0);
19 % Pack T and Y into a matrix and return as result
20 res = [T,Y];
21 % Plotting
22 if flag == 1
23 plot(T,Y,'-r')
24 else
25 plot(T,Y,'-b')
26 end
27 xlabel('time [months]')
28 ylabel('field mice population')
29
30 function res = field_mice_rate(t,y)
31 % Interpolate to find b
32 if flag == 1
33 b = interpolate_b(t);
34 else
35 b = interpolate_b_spline(t);
36 end
37
38 res = a*y-b*y^(1.4);
39 end
40
41 end
456 Chapter 12 Interpolation

Listing 12.5 interpolate_b_field_mice.m


1 function res = interpolate_b_field_mice(t,flag)
2 T = 0:8;
3 B = [70 36 11 1 4 13 28 43 56] * 1e-4;
4 % If flag == 1, use linear interpolation, else use spline
5 if flag == 1
6 res = interp1(T, B, t,'linear',NaN);
7 else
8 res = interp1(T, B, t,'spline',NaN);
9 end
10 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')

×10 4
7
linear interpolation
spline interpolation
6

5
field mice population

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
11 spline interpolation

10
ln(field mice population)

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')

×10 -3
7
ref data
linear
spline
6

4
b

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 459

following data:

t c (t )
0 1.0000
2 7.3891
4 54.5982
6 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')
460 Chapter 12 Interpolation

450
ref data
400 exp(t)
linear
spline
350 pchip

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.
t Remember in MATLAB log
>> LOGC = log(C); is ln, and log10 is log.
>> 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
6
linear
spline
pchip
5

4
ln c

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.
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] P sat [bar]


300 0.1381
320 0.32029
340 0.66128
360 1.2425
380 2.1614
400 3.5284
420 5.4649
440 8.1007
460 11.575
480 16.037
500 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 temper-
ature 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; break it up onto two lines.
>> Psat_bar = [0.1381,0.32029,0.66128,1.2425,2.1614,3.5284,5.4649,... To continue onto another
8.1007,11.575,16.037,21.651]; line, we need to add ... to
the end of the line. Then to
get to the next line in the
Next, to graphically compare the use of linear and spline interpolation, let’s use Command Window, use
linear and spline interpolation to estimate the vapor pressure over the range 300 Shift + Enter. Know that
to 500 K in increments of 1 K. To do this, we will create a new vector T_eval which if I were to omit the ...,
contains the temperatures we wish to estimate the vapor pressure at. We can then MATLAB would interpret the
use interp1 with either linear or spline interpolation, and store the results to two second line as the second
new vectors. We will then plot the original data and our interpolation results, and column in matrix.
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')
464 Chapter 12 Interpolation

25
reference
linear
spline
20

15

Psat [bar]
10

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
3 spline

2
ln Psat/bar

-1

-2
2 2.2 2.4 2.6 2.8 3 3.2 3.4
1/T [1/K] ×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 difficul- We will download the saturated steam tables from the NIST WebBook Thermo-
ties downloading a data set physical Properties of Fluid Systems.3 While here we will look at water, other
from NIST, a copy of my fluids are available, and the database keeps growing.
file for water can be down- For the species, from the drop-down menu select “Water,” and then choose
loaded here.
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 down- t I mention again that if you
load the tabulated data. This can be accomplished by right-clicking on “Download are having difficulties down-
data as tab-deliminated text file.” On my computer the default name comes up as loading a data set from
“fluid.cgi.” I will save to my current working directory as “water_sat_nist.dat.” The NIST, a copy of my file for
other useful piece of information to note before closing our web browser is the water can be downloaded
here.
“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
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 Thermophys-
ical 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 Now that we have tabulated data, we need to read it into MATLAB. There are
more about importing data several ways to read-in data in MATLAB. Here we will use dlmread which is well
into MATLAB, have a look
suited for this case. The “dlm” stands for a “deliminated” text file. A deliminated
at the documentation page
“Ways to Import Text Files”.
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); different directory, you
could still load it, you would
just need to augment its
I suppressed the output because the saturated steam tables contain a large name to provide its loca-
amount of data. If we would like to check the size of M, we can: tion. If the file were in my
home directory, located
>> whos M at “/home/paluchas”, we
Name Size Bytes Class Attributes could replace the file name
M 37x25 7400 double with “/home/paluchas/wa-
ter_sat_nist.dat”. You can
always use global com-
So M is a matrix with 37 rows and 25 columns. What does each row correspond to? mands like this. Local com-
You could find out by viewing “water_sat_nist.dat” in your favorite text editor. To mands are also possible if
view the file within the MATLAB Command Window, you can use the command you know where the file is
type ’water_sat_nist.dat’. I do not do so here in the text because the file in relation to the current
is very large. You might find that the columns do not appear to be exactly lined directory.
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:
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) 471

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:
472 Chapter 12 Interpolation

Listing 12.6 import_sat_vle.m


1 % function res = import_sat_vle(sat_vle_nist)
2 %
3 % Imporating tabulated saturated VLE data from NIST
4 %
5 % res will be the matrix of desired properties (type numeric).
6 % By default this will by T, P, rho L, U L, H L, S L,
7 % rho V, U V, H V, S V
8 % but you can easily modify this.
9 %
10 % sat_vle_nist will be a string corresponding to the file name.
11 % be sure to include the path if it is not in the current
12 % directory.
13 %
14 function res = import_sat_vle(sat_vle_nist)
15 % Read in our saturated VLE data from NIST, skipping
16 % the first row which corresponds to the column labels
17 M=dlmread(sat_vle_nist,'\t',1,0);
18 % Columns are labeled as:
19 % 1) Temperature (K)
20 % 2) Pressure (MPa)
21 % 3) Density (l, mol/l)
22 % 4) Volume (l, l/mol)
23 % 5) Internal Energy (l, kJ/mol)
24 % 6) Enthalpy (l, kJ/mol)
25 % 7) Entropy (l, J/mol*K)
26 % 8) Cv (l, J/mol*K)
27 % 9) Cp (l, J/mol*K)
28 %10) Sound Spd. (l, m/s)
29 %11) Joule-Thomson (l, K/bar)
30 %12) Viscosity (l, uPa*s)
31 %13) Therm. Cond. (l, W/m*K)
32 %14) Surf. Tension (l, N/m)
33 %15) Density (v, mol/l)
34 %16) Volume (v, l/mol)
35 %17) Internal Energy (v, kJ/kg)
36 %18) Enthalpy (v, kJ/kg)
37 %19) Entropy (v, J/g*K)
38 %20) Cv (v, J/g*K)
39 %21) Cp (v, J/g*K)
40 %22) Sound Spd. (v, m/s)
41 %23) Joule-Thomson (v, K/MPa)
42 %24) Viscosity (v, Pa*s)
43 %25) Therm. Cond. (v, W/m*K)
44
45 % Creating a matrix with specified columns.
46 % By default this will by T, P, rho L, U L, H L, S L,
47 % rho V, U V, H V, S V
48 % but you can easily modify this. This will be returned.
49 res = [M(:,1:3),M(:,5:7),M(:,15),M(:,17:19)];
50 %res = [M(:,1),M(:,14)];
51 end
12.7 Fun with tabulated pure component VLE data (i.e., the saturated steam tables) 473

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 spec-
ify 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 thermo-
dynamic 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 0.1048 0.0004

Columns 7 through 10

0.0000 2.4091 2.5465 0.0086

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 individ-
ual 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:
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

Columns 4 through 6

0.104824990686921 0.104824285101334 0.000367225663839

Columns 7 through 9

0.000000023072679 2.409079489426976 2.546546392886929

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) 475

sol =
Columns 1 through 4

2.9815e+02 3.1695e-03 9.9700e-01 1.0482e+02

Columns 5 through 8

1.0482e+02 3.6723e-01 2.3073e-05 2.4091e+03

Columns 9 through 10

2.5465e+03 8.5567e+00

Besides temperature, you can interpolate in any variable you wish. For exam-
ple, 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

Columns 5 through 8

5.6143e+02 1.6718e+00 1.6509e-03 2.5431e+03

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:
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

Columns 5 through 8

1.0821e+03 2.7867e+00 1.9709e-02 2.6020e+03

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) 477

650

600

550
Temperature [K]

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.
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

Columns 5 through 8

2.0953e+03 4.4283e+00 3.1966e-01 2.0185e+03

Columns 9 through 10

2.0949e+03 4.3552e+00

>> plot(sol(3),sol(1),'kx')

700

650

600

550
Temperature [K]

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) 479

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

550

500
Temperature [K]

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')
480 Chapter 12 Interpolation

650

600

550

500
Temperature [K]
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) T (◦ C)
1101.0 25.113
911.3 30.131
636.0 40.120
451.1 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 phase-
envelope, 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 unap-
preciated. 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.
Chapter 13
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 so-
lution 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:
1 3 ¯¯2 1 3 1 3
Z 2 · ¸¯
2
x dx = x ¯ = 2 − 0 = 8/3 = 2.6667
0 3 0 3 3
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 ap-
proximations to solve definite integrals. We often desire to employ numerical
techniques for integration for a few reasons. First, as shown in expression sec-
tion 13.1, our integral expression might be impossible to evaluate analytically, or
at least without elementary functions, preventing us from solving by hand. An-
other 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 n
f x i∗ ∆x
X ¡ ¢
f (x) = lim
a n→∞
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 inte-
grals. 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 tech-
nique 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

0
0 0.5 1 1.5 2
Figure 13.1 The left-hand sum of x 3 over the range 0 to 2. Image from
Wikipedia.
486 Chapter 13 Numerical Integration

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.

0
0 0.5 1 1.5 2
Figure 13.3 The midpoint sum of x 3 over the range 0 to 2. Image from
Wikipedia.
13.2 Numerical Integration 487

We see that the computed integral wil be sensitive to how the rectangles are con-
structures. 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

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:

f (x 0 + ∆x) − f (x 0 )
m=
∆x
and
f (x 0 + ∆x) − f (x 0 )
b = f (x 0 ) − 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 =
¯
x0 2 x 2
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 in-
tegrating. 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 func-
tion 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. Pa-
rameters 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 = π
0 2
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:


Z 7
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

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 in-
tegrals, 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
ig ig CP P2
∆S ig = S 2 (T2 , P 2 ) − S 1 (T1 , P 1 ) = d T − R ln
T1 T P1
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
= a0 + a1 T + a2 T 2 + a3 T 3 + a4 T 4
R
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 .
Chapter 14
Monte Carlo

In this chapter we will briefly discuss the topic of Monte Carlo sampling (or in-
tegration). 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 thermodynam-
ics, 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

0.8

0.6

0.4

0.2

-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 gen-
erators. 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 re-
produce 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 % Function to perform Monte Carlo simulation to estimate the value
2 % of pi. This is accomplished by computing the area of a circle of
3 % radius 1, for which the area is equal to pi. The bounding square
4 % will be of area (2*r)^(2) = 4.
5
6 function res = circle_area(n)
7 % Generate a vector of random numbers of length n.
8 % We will have one vector corresponding to the x value and another
9 % corresponding to the y value.
10 %
11 % The numbers will be uniformly distributed between 0 and 1.
12 % We want numbers between -1 and 1. So first multiply by 2, so the
13 % numbers are between 0 and 2, then shift by -1 so they are between
14 % -1 and 1.
15 X = 2*rand(1,n)-1;
16 Y = 2*rand(1,n)-1;
17
18 % The equation of our circle is x^2 + y^2 = 1. Check to see if
19 % our x,y coordinates fall within the circle. (I.e., <= 1)
20 Circle = X.^(2) + Y.^(2);
21 % Performing a logical vector comparison.
22 Circle_hits = Circle <= 1;
23 % This is logical vector, so 1 for true and 0 for false. So if
24 % we sum the vector, that will give the number of hits. If we divide
25 % by the length of the vector (or n), that gives us the fraction that
26 % were hits. Since the area of the bounding square is (2*r)^(2)=4,
27 % multiply the fraction of hits by the area of the square to get
28 % the area of the cirle. Since r=1, the area of the circle
29 % is pi.
30 frac_hits = sum(Circle_hits)/n;
31 res = frac_hits*4;
32 end

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 % This script is a simple for loop to estimate values of pi using
2 % the function circle_area for a range of number of Monte Carlo trials.
3
4 % Number of trials
5 N = 10:10:1e5;
6
7 % Initialize our vector of estimates of pi
8 Sol = ones(1,length(N));
9
10 for i=1:length(N)
11 Sol(i) = circle_area(N(i));
12 end
13
14 hold on
15 plot(N,Sol,'-ko')
16 ylabel('estimate of pi')
17 xlabel('number of trials')
18
19 % Now plot as a reference y=pi
20 Ref = pi * ones(1,length(N));
21 plot(N,Ref,'-r')
14.2 Back to π 499

3.5

3.4

3.3

3.2
estimate of pi

3.1

2.9

2.8

2.7

2.6
0 2 4 6 8 10
number of trials 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 tri-
als (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 % Function to perform Monte Carlo simulation to estimate the value
2 % of pi. This is accomplished by computing the area of a circle of
3 % radius 1, for which the area is equal to pi. The bounding square
4 % will be of area (2*r)^(2) = 4.
5 %
6 % In this version of the code I will the square, circle, and trial
7 % points.
8
9 function res = circle_area_plot_trials(n)
10 % Generate a vector of random numbers of length n.
11 % We will have one vector corresponding to the x value and another
12 % corresponding to the y value.
13 %
14 % The numbers will be uniformly distributed between 0 and 1.
15 % We want numbers between -1 and 1. So first multiply by 2, so the
16 % numbers are between 0 and 2, then shift by -1 so they are between
17 % -1 and 1.
18 X = 2*rand(1,n)-1;
19 Y = 2*rand(1,n)-1;
20
21 % The equation of our circle is x^2 + y^2 = 1. Check to see if
22 % our x,y coordinates fall within the circle. (I.e., <= 1)
23 Circle = X.^(2) + Y.^(2);
24 % Performing a logical vector comparison.
25 Circle_hits = Circle <= 1;
26 % This is logical vector, so 1 for true and 0 for false. So if
27 % we sum the vector, that will give the number of hits. If we divide
28 % by the length of the vector (or n), that gives us the fraction that
29 % were hits. Since the area of the bounding square is (2*r)^(2)=4,
30 % multiply the fraction of hits by the area of the square to get
31 % the area of the cirle. Since r=1, the area of the circle
32 % is pi.
33 frac_hits = sum(Circle_hits)/n;
34 res = frac_hits*4;
35
36 hold on
37 % Plot the trials. We plot them first so that the circle will
38 % be printed on top.
39 plot(X,Y,'ko')
40
41 % Let's start by plotting the square
42 Xs = [-1,1,1,-1,-1];
43 Ys = [1,1,-1,-1,1];
44 plot(Xs,Ys,'-k')
45
46 % Next, we plot the circle
47 % Creating a vector of angles. Let's plot 100 points
48 theta = 0:pi/100:2*pi;
49 Xc = cos(theta);
50 Yc = sin(theta);
51 plot(Xc,Yc,'-r')
52
53 end
14.2 Back to π 501

0.8

0.6

0.4

0.2

-0.2

-0.4

-0.6

-0.8

-1
-1 -0.5 0 0.5 1

Figure 14.3 10 trials.

0.8

0.6

0.4

0.2

-0.2

-0.4

-0.6

-0.8

-1
-1 -0.5 0 0.5 1

Figure 14.4 1,000 trials.


502 Chapter 14 Monte Carlo

0.8

0.6

0.4

0.2

-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 b
I= 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 503

(≤ y). If it is, it is a hit. As before, we compute the frequency of hits, ρ = NNtrials


hits
. 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 % function [res,ref] = xsq_area(n,X_range)
2 %
3 % Function to compute the integral of x^2 using Monte Carlo integration
4 % Inputs: n, the number of trials
5 % X_range, a vector of length 2 containing the range of
6 % integration. The first element should be the lower
7 % bound and the second value should be the upper bound.
8 % Outpusts: res, the integral as computed using Monte Carlo integration
9 % ref, the analytic solutions
10 %
11 function [res,ref] = xsq_area(n,X_range)
12 % We need to size our bounding box. We know the smallest y value will be
13 % 0, and here we find the largest y value.
14 Y_ends = X_range.^(2);
15 Y_range = [0,max(Y_ends)];
16 % Since we know that ymax will occur at the upper bound in our x range,
17 % we could just as well use Y_ends(end) or Y_ends(2).
18
19 % Generate the x coordinate of our trials. X spans the range of
20 % X_range. Remember rand generates numbers uniformly over the range 0
21 % to 1. So first we multiple by the range of x values, and then shift
22 % by the lower bound.
23 X_trial = (X_range(2)-X_range(1))*rand(1,n)+X_range(1);
24 % Generate the y coordinate of our trials. We will generate our y
25 % coorindates in the same way. While we could simplify this for this
26 % problem, we will write the expression in general.
27 Y_trial = (Y_range(2)-Y_range(1))*rand(1,n)+Y_range(1);
28
29 % Determine number of hits. This will be when the coordinates of the
30 % trial are less than f(x). In general we could create a function file
31 % for f(x), but here x^2 is easy enough.
32 N_hits = Y_trial <= X_trial.^(2);
33
34 % The fraction of trials that are hits
35 frac_hits = sum(N_hits)/n;
36
37 % Multiplying the fraction of hits by the area of the bounding box to
38 % get the integral (or area) of x^2.
39 res = frac_hits*(Y_range(2)-Y_range(1))*(X_range(2)-X_range(1));
40
41 % The analaytic solution for comparison
42 ref = (1/3)*(X_range(2)^(3)-X_range(1)^(3));
43 end
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 esti-
mate 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:
Z b
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 Z b
p (x) d x = C d x = C (b − a) = 1
a a
For this to be true, it must be that:

1
p (x) =
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 505

Z b
I= f (x) d x
a
Z b
(b − a)
= f (x) dx
a (b − a)
Z b
1
= (b − a) f (x) dx
a (b − a)
Z b
= (b − a) f (x) p (x) d 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 b ­ n
® (b − a) X
I= f (x) d x = (b − a) f (x) ≈ f (x i )
a 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 .
506 Chapter 14 Monte Carlo

Listing 14.5 xsq_area_simple.m


1 % function [res,ref] = xsq_area_simple(n,X_range)
2 %
3 % Function to compute the integral of x^2 using Monte Carlo integration
4 % Inputs: n, the number of trials
5 % X_range, a vector of length 2 containing the range of
6 % integration. The first element should be the lower
7 % bound and the second value should be the upper bound.
8 % Outpusts: res, the integral as computed using Monte Carlo integration
9 % ref, the analytic solutions
10 %
11 function [res,ref] = xsq_area_simple(n,X_range)
12 % Generate the x coordinate of our trials. X spans the range of
13 % X_range. Remember rand generates numbers uniformly over the range 0
14 % to 1. So first we multiple by the range of x values, and then shift
15 % by the lower bound.
16 X_trial = (X_range(2)-X_range(1))*rand(1,n)+X_range(1);
17 % Evaluate our function at the x trial values
18 Y = X_trial.^(2);
19 % Estimate the integral using MC
20 res = sum(Y)./n*(X_range(2)-X_range(1));
21
22 % The analaytic solution for comparison
23 ref = (1/3)*(X_range(2)^(3)-X_range(1)^(3))
24 end

>> [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 507

Listing 14.6 monte_carlo_int.m


1 % function res = monte_carlo_int(fcn_handle,n,X_range)
2 %
3 % Function to compute the integral of a function using Monte Carlo
4 % integration. The method is restricted to functions that are positive,
5 % and the integral must be finite.
6 % Inputs: fcn_handle, function handle for the function you wish to
7 % integrate. Note that for this code to work, the
8 % function must be vectorized.
9 % n, the number of trials
10 % X_range, a vector of length 2 containing the range of
11 % integration. The first element should be the lower
12 % bound and the second value should be the upper bound.
13 % Outpusts: res, the integral as computed using Monte Carlo integration.
14 %
15 function res = monte_carlo_int(fcn_handle,n,X_range)
16 % Generate the x coordinate of our trials. X spans the range of
17 % X_range. Remember rand generates numbers uniformly over the range 0
18 % to 1. So first we multiple by the range of x values, and then shift
19 % by the lower bound.
20 X_trial = (X_range(2)-X_range(1))*rand(1,n)+X_range(1);
21 % Evaluate our function at the x trial values
22 Y = fcn_handle(X_trial);
23 % Estimate the integral using MC
24 res = sum(Y)./n*(X_range(2)-X_range(1));
25 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:
Z 2π
sin (x) d x = 0
0

>> fun = @(X) sin(X);


>> sol = monte_carlo_int(fun,1e6,[0,2*pi])
sol = -0.0018
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

-1 -0.5 0 0.5 1

Figure 15.1 fplot(f,[-1,1],’-k’)


Let’s consider also the case of:
ex
g (x) =
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 511

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.


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

>> syms x
>> assume(x,'real')
>> l1 = limit(x,x,4)
l1 = 4

lim x 2
x→2

>> l2 = limit(x^2,x,2)
l2 = 4

x2 + x + 2
lim
x→1 x +1
>> l3 = limit((x^2+x+2)/(x+1),x,1)
l3 = 2

p
lim x
x→2

>> 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 indetermi-
nate 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
lim
x→0 x
>> l6 = limit((exp(2*x)-1)/x,x,0)
l6 = 2

ln x
lim
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 (positive-
direction) 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

0.8

0.6

0.4

0.2

-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
dx ∆x→0 ∆x
Or equivalently we will use the following notation where ∆x = h:

df f (x + h) − f (x)
= f 0 (x) = lim
dx h→0 h

Using Matlab, we can calculate the differential of a function using this def-
inition 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 517

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
2
x =3 x = 6x
dx dx
d3 3 d
3
x =6 x =6
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 in-
puts: (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.

1
Z
xd x = x 2 + c
2
518 Chapter 15 An Introduction to Symbolic Calculations

where c is our constant of integration.


Z 2 1 £ 2 ¤¯¯2 1 £ 2
x 0 = 2 − 02 = 2
¤
xd x =
0 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
Z b
xd x = 11
0

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 521

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 coef-
ficients 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 un-
known. 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 han-
dle systems of linear and non-linear equations too, where we had used fsolve
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. Be-
yond 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 nu-
mericall. 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 523

>> 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))
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 first-


order 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. Un-
fortunately, 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:

d 2C A k
− ◦ 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 527

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

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!
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
530 Chapter A while loops

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 function res = sum_for(X)


2 c = 0;
3 for i=1:length(X)
4 c = c + X(i);
5 end
6 res = c;
7 end

We can write an equivalent function using a while statement as:


A.2 while loops 531

Listing A.2 sum_while.m

1 function res = sum_while(X)


2 c = 0;
3 i = 0;
4 while i < length(X)
5 i=i+1;
6 c=c+X(i);
7 end
8 res = c;
9 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 flexibil- t Remember, the while state-
ity when there are multiple different reasons to exit a loop without needing to ment executes while the
conditional statement is
remember the break command. Here is how we could use a flag variable in our
true. You could therefore
summation example: just assign flag a value
of one initially, flag = 1.
Listing A.3 sum_flag.m Since to MATLAB a value
of 1 means true, your con-
1 function res = sum_flag(X) ditional statement could
2 c=0; be just flag. Then when
3 i=0; you want to exit, assign to
4 flag = 0; flag a value of zero, false.
5 while flag == 0 To me, explicitly having a
6 i=i+1; logical statement of flag
== 1, while using more
7 c=c+X(i);
characters, makes my code
8 if i == length(X) easier to understand. And
9 flag = 1; if it is easier to understand,
10 end I am less likely to make a
11 end mistake.
12 res = c;
13 end

All three functions yield identical results:


532 Chapter A while loops

>> 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

You might also like