You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: book/book.tex
+34-22Lines changed: 34 additions & 22 deletions
Original file line number
Diff line number
Diff line change
@@ -13,7 +13,7 @@
13
13
\newcommand{\thetitle}{Modeling and Simulation in Python}
14
14
\newcommand{\thesubtitle}{}
15
15
\newcommand{\theauthors}{Allen B. Downey}
16
-
\newcommand{\theversion}{0.13.0}
16
+
\newcommand{\theversion}{0.15.0}
17
17
18
18
19
19
%%%% Both LATEX and PLASTEX
@@ -2798,7 +2798,7 @@ \section{Now with a TimeFrame}
2798
2798
2799
2799
If the number of state variables is small, storing them as separate \py{TimeSeries} objects might not be so bad. But a better alternative is to use a \py{TimeFrame}, which is another object are defined in the \py{modsim} library.
2800
2800
2801
-
A \py{TimeFrame} is almost identical to a \py{DataFrame}, which we used in Section~\ref{worldpopdata}, with just a few changes I made to make them easier to use for our purposes. For more details, you can read the \py{modsim} library source code.
2801
+
A \py{TimeFrame} is almost identical to a \py{DataFrame}, which we used in Section~\ref{worldpopdata}, with just a few changes I made to adapt it for our purposes. For more details, you can read the \py{modsim} library source code.
2802
2802
2803
2803
Here's a more concise version of \py{run_simulation} using a \py{TimeFrame}:
2804
2804
@@ -2813,13 +2813,13 @@ \section{Now with a TimeFrame}
2813
2813
system.results = frame
2814
2814
\end{python}
2815
2815
2816
-
The first line creates an empty \py{TimeFrame} with one column for each state variables. Then, before the loop starts, we store the initial conditions in the \py{TimeFrame} at \py{t0}. Based on the way we've been using \py{TimeSeries} objects, it is tempting to write:
2816
+
The first line creates an empty \py{TimeFrame} with one column for each state variable. Then, before the loop starts, we store the initial conditions in the \py{TimeFrame} at \py{t0}. Based on the way we've been using \py{TimeSeries} objects, it is tempting to write:
2817
2817
2818
2818
\begin{python}
2819
2819
frame[system.t0] = system.init
2820
2820
\end{python}
2821
2821
2822
-
But when you use the bracket operator with a \py{TimeFrame} or \py{DataFrame}, it selects a column, not a row. For example, to select a column, we could write
2822
+
But when you use the bracket operator with a \py{TimeFrame} or \py{DataFrame}, it selects a column, not a row. For example, to select a column, we could write:
2823
2823
2824
2824
\begin{python}
2825
2825
frame['S']
@@ -2854,7 +2854,7 @@ \section{Now with a TimeFrame}
2854
2854
\section{Metrics}
2855
2855
\label{metrics}
2856
2856
2857
-
When we plot a time series, we get a view of everything that happened when the model ran, but often we want to boil it down to a few numbers, ideally one, that summarize the outcome. These summary statistics are called {\bf metrics}.
2857
+
When we plot a time series, we get a view of everything that happened when the model ran, but often we want to boil it down to a few numbers that summarize the outcome. These summary statistics are called {\bf metrics}.
2858
2858
2859
2859
In the SIR model, we might want to know the time until the peak of the outbreak, the number of people who are sick at the peak, the number of students who will still be sick at the end of the semester, or the total number of students get sick at any point.
2860
2860
@@ -2891,7 +2891,7 @@ \section{Immunization}
2891
2891
2892
2892
Suppose there is a vaccine that causes a patient to become immune to the Freshman Plague without being infected. How might you modify the model to capture this effect?
2893
2893
2894
-
One option is to treat immunization as a short cut from \py{S} to \py{R}, that is, from susceptible to removed (or resistant). We can implement this feature like this:
2894
+
One option is to treat immunization as a short cut from \py{S} to \py{R}, that is, from susceptible to resistant\footnote{We can be flexible about what \py{R} stands for.}. We can implement this feature like this:
2895
2895
2896
2896
\begin{python}
2897
2897
def add_immunization(system, fraction):
@@ -3504,7 +3504,7 @@ \section{Newton's law of cooling}
3504
3504
%
3505
3505
\[\frac{dT}{dt} = -r (T - T_{env}) \]
3506
3506
%
3507
-
where $T$, the temperature of the object, is a function of time, $t$; $T_{env}$ is the temperature of the environment, and $r$ is a constant that characterizes how quickly heats moves across the boundary between the system and the environment.
3507
+
where $T$, the temperature of the object, is a function of time, $t$; $T_{env}$ is the temperature of the environment, and $r$ is a constant that characterizes how quickly heats is transferred between the system and the environment.
3508
3508
3509
3509
Newton's so-called ``law" is really a model in the sense that it is approximately true in some conditions, only roughly true in others, and not at all true in others.
The initial state of the model is $X(0) = I_b$ and $G(0) = G_0$, where $G_0$ is a constant that represents the concentration of blood sugar immediately after the injection. In theory we could estimate $G_0$ based on measurements, but in practice it takes time for the injected glucose to spread through the blood volume. Since $G_0$ is not measurable, it is treated as a {\bf free parameter} of the model, which means that we are free to chose it to fit the data.
3965
+
The initial state of the model is $X(0) = I_b$ and $G(0) = G_0$, where $G_0$ is a constant that represents the concentration of blood sugar immediately after the injection. In theory we could estimate $G_0$ based on measurements, but in practice it takes time for the injected glucose to spread through the blood volume. Since $G_0$ is not measurable, it is treated as a {\bf free parameter} of the model, which means that we are free to choose it to fit the data.
3966
3966
3967
3967
\section{Data}
3968
3968
3969
-
To develop and test the model, I'll use data from Pacini and Bergman\footnote{"MINMOD: a computer program to calculate insulin sensitivity and pancreatic responsivity from the frequently sampled intravenous glucose tolerance test", {\em Computer Methods and Programs in Biomedicine} 23: 113-122, 1986.}. The dataset is in a file in the repository for this book, which we can read into a \py{DataFrame}:
3969
+
To develop and test the model, I'll use data from Pacini and Bergman\footnote{``MINMOD: A computer program to calculate insulin sensitivity and pancreatic responsivity from the frequently sampled intravenous glucose tolerance test", {\em Computer Methods and Programs in Biomedicine} 23: 113-122, 1986.}. The dataset is in a file in the repository for this book, which we can read into a \py{DataFrame}:
3970
3970
3971
3971
\begin{python}
3972
3972
data = pd.read_csv('glucose_insulin.csv', index_col='time')
@@ -4380,11 +4380,11 @@ \section{Newton's second law of motion}
4380
4380
%
4381
4381
\[\frac{d^2y}{dx^2} = H(x, y, y') \]
4382
4382
%
4383
-
where $H$ is another function and $y'$ is shorthand for $dy/dx$. In particular, we will work with one of the most famous, useful, and possibly the first second order ODE, Newton's second law of motion:
4383
+
where $H$ is another function and $y'$ is shorthand for $dy/dx$. In particular, we will work with one of the most famousand useful second order ODEs, Newton's second law of motion:
4384
4384
%
4385
4385
\[ F = m a \]
4386
4386
%
4387
-
where $F$ is a force or the total of a set of forces, $m$ is the mass of a moving object, and $a$ is acceleration.
4387
+
where $F$ is a force or the total of a set of forces, $m$ is the mass of a moving object, and $a$ is its acceleration.
4388
4388
4389
4389
Newton's law might not look like a differential equation, until we realize that acceleration, $a$, is the second derivative of position, $y$, with respect to time, $t$. With the substitution
4390
4390
%
@@ -4408,14 +4408,14 @@ \section{Newton's second law of motion}
4408
4408
4409
4409
\end{itemize}
4410
4410
4411
-
But for medium to large things with constant mass, moving at speeds that are medium to slow, Newton's model is a phenomenally useful. If we can quantify the forces that act on such an object, we can figure out how it will move.
4411
+
But for medium to large things with constant mass, moving at speeds that are medium to slow, Newton's model is phenomenally useful. If we can quantify the forces that act on such an object, we can figure out how it will move.
4412
4412
4413
4413
4414
4414
\section{Dropping pennies}
4415
4415
4416
4416
As a first example, let's get back to the penny falling from the Empire State Building, which we considered in Section~\ref{penny}. We will implement two models of this system: first without air resistance, then with.
4417
4417
4418
-
Given that the Empire State Building is \SI{381}{\meter} high, and assuming that the penny is dropped with 0 velocity, the initial conditions are:
4418
+
Given that the Empire State Building is \SI{381}{\meter} high, and assuming that the penny is dropped with velocity zero, the initial conditions are:
4419
4419
4420
4420
\begin{python}
4421
4421
init = State(y=381 * m,
@@ -4448,7 +4448,7 @@ \section{Dropping pennies}
4448
4448
4449
4449
\py{ts} contains equally spaced points between \SI{0}{\second} and \SI{10}{\second}, including both endpoints.
4450
4450
4451
-
Next we need a \py{System} object to contains the system parameters:
4451
+
Next we need a \py{System} object to contain the system parameters:
4452
4452
4453
4453
\begin{python}
4454
4454
system = System(init=init, g=g, ts=ts)
@@ -4495,7 +4495,7 @@ \section{Dropping pennies}
4495
4495
slope_func(init, 0, system)
4496
4496
\end{python}
4497
4497
4498
-
The result is a sequence containing \SI{0}{\meter\per\second} for velocity and \SI{-9.8}{\meter\per\second\squared} for acceleration. Now we can run \py{odeint} like this:
4498
+
The result is a sequence containing \SI{0}{\meter\per\second} for velocity and \SI{9.8}{\meter\per\second\squared} for acceleration. Now we can run \py{odeint} like this:
4499
4499
4500
4500
\begin{python}
4501
4501
run_odeint(system, slope_func)
@@ -4607,9 +4607,9 @@ \section{With air resistance}
4607
4607
\section{Implementation}
4608
4608
\label{condition}
4609
4609
4610
-
As the number of system parameters increases, and as we need to do more work to compute them, we will find it useful to define a \py{Condition} object to contain the quantities we need to make a \py{System} object.
4610
+
As the number of system parameters increases, and as we need to do more work to compute them, we will find it useful to define a \py{Condition} object to contain the quantities we need to make a \py{System} object.\py{Condition} objects are similar to \py{System} and \py{State} objects; in fact, all three have the same capabilities. I have given them different names to document the different roles they play.
4611
4611
4612
-
The way we create a \py{Condition} object is the same as for \py{System} and \py{State} objects. In fact, these three object types have the same capabilities; I have given them different names just to identify their different roles. Here's the \py{Condition} object for the falling penny:
4612
+
Here's the \py{Condition} object for the falling penny:
4613
4613
4614
4614
\begin{python}
4615
4615
condition = Condition(height = 381 * m,
@@ -5068,11 +5068,23 @@ \section{Finding the range}
5068
5068
5069
5069
\py{interpolate_range} uses \py{idxmax} to find the time when the ball hits its peak (see Section~\ref{metrics}).
5070
5070
5071
-
Then it uses \py{loc} to extract only the points after \py{t_peak}. The index here is a {\bf slice}. In general, you can use a slice to select a range of values from a \py{Series}, specifying the start and end indices. In this example we only specify the beginning, so we get everything from \py{t_peak} the end of the \py{Series}.
5071
+
Then it uses \py{ys.loc} to extract only the points from \py{t_peak} to the end. The index in brackets includes a colon (\py{:}), which indicates that it is a {\bf slice index}. In general, a slice selects a range of elements from a \py{Series}, specifying the start and end indices. For example, the following slice selects elements from \py{t_peak} to \py{t_land}, including both:
5072
+
5073
+
\begin{python}
5074
+
ys.loc[t_peak:t_land]
5075
+
\end{python}
5076
+
5077
+
If we omit the first index, like this:
5078
+
5079
+
\begin{python}
5080
+
ys.loc[:t_land]
5081
+
\end{python}
5082
+
5083
+
we get everything from the beginning to \py{t_land}. If we omit the second index, as in \py{interpolate_range}, we get everything from \py{t_peak} to the end of the \py{Series}. For more about indexing with \py{loc}, see \url{https://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.loc.html}.
5072
5084
5073
5085
Next it uses \py{interp_inverse}, which we saw in Section~\ref{sidewalk}, to make \py{T}, which is an interpolation function that maps from height to time. So \py{t_land} is the time when height is 0.
5074
5086
5075
-
Finally, it uses \py{interpolate} to make \py{X}, which is an interpolation function that maps from time to $x$ component. So \py{X(T_land)} is the distance from home plate when the ball lands.
5087
+
Finally, it uses \py{interpolate} to make \py{X}, which is an interpolation function that maps from time to $x$ component. So \py{X(t_land)} is the distance from home plate when the ball lands.
5076
5088
5077
5089
We can call \py{interpolate_range} like this:
5078
5090
@@ -5158,7 +5170,7 @@ \chapter{Rotation}
5158
5170
5159
5171
If the configuration of an object changes over time, it might become easier or harder to spin, which explains the surprising dynamics of gymnasts, divers, ice skaters, etc.
5160
5172
5161
-
And when you applying a twisting force to a rotating object, the effect is often contrary to intuition. For an example, see this video on gyroscopic precession \url{https://www.youtube.com/watch?v=ty9QSiVC2g0}.
5173
+
And when you apply a twisting force to a rotating object, the effect is often contrary to intuition. For an example, see this video on gyroscopic precession \url{https://www.youtube.com/watch?v=ty9QSiVC2g0}.
5162
5174
5163
5175
In this chapter, we will not take on the physics of rotation in all its glory. Rather, we will focus on simple scenarios where all rotation and all twisting forces are around a single axis. In that case, we can treat some vector quantities as if they were scalars (in the same way that we sometimes treat velocity as a scalar with an implicit direction).
5164
5176
@@ -5421,7 +5433,7 @@ \section{Moment of inertia}
5421
5433
%
5422
5434
Where $\alpha$ is angular acceleration and $I$ is {\bf moment of inertia}. Just as mass is what makes it harder to accelerate an object\footnote{That might sound like a dumb way to describe mass, but its actually one of the fundamental definitions.}, moment of inertia is what makes it harder to spin an object.
5423
5435
5424
-
In the most general case, a 3D object rotating around an arbitrary axis, moment of inertia is a tensor quantity, which is like a function that takes a vector as a parameter and returns a vector as a result.
5436
+
In the most general case, a 3-D object rotating around an arbitrary axis, moment of inertia is a tensor, which is a function that takes a vector as a parameter and returns a vector as a result.
5425
5437
5426
5438
Fortunately, in a system where all rotation and torque happens around a single axis, we don't have to deal with the most general case. We can treat moment of inertia as a scalar quantity.
0 commit comments