600.112: Intro Programming For Scientists and Engineers Assignment 6: Heat Transfer
600.112: Intro Programming For Scientists and Engineers Assignment 6: Heat Transfer
when we bring the two cubes into “perfect” physi- Then we can in turn approximate the second
cal contact? derivative at pm like this:
Along the area of contact, the atoms of the 75◦ C
∂T
∂ 2 T − ∂T
cube will vibrate faster than those of the 25◦ C
∂x m+1/2 ∂x m−1/2
≈
cube. Every now and then a 75◦ C atom will “hit” a ∂x2 m ∆x
25◦ C atom and transfer its “kinetic energy” in the Tm+1 −Tm
− Tm −Tm−1
∆x ∆x
process, creating a 75◦ C atom in the 25◦ C cube and ≈
∆x
vice versa. Since this happens all along the area Tm+1 − Tm − Tm + Tm−1
of contact, pretty soon we’ll have an even mixture ≈
∆x2
of 75◦ C and 25◦ C atoms wiggling around in these Tm+1 − 2Tm + Tm−1
two “layers” of each cube. So on average, across ≈
∆x2
all the atoms along the area of contact, we have a
temperature of (25◦ C + 75◦ C) ÷ 2 = 50◦ C. Our approximation for one-dimensional heat trans-
What about the “next two layers” of atoms in fer by conduction becomes
each cube? They will in turn “exchange” equal Tm+1 − 2Tm + Tm−1
amounts of 75◦ C and 25◦ C atoms with the contact =0
∆x2
layers, and so on. Eventually the average tempera-
ture of both cubes will be 50◦ C, and it will remain which—when solved for the point pm —confirms
50◦ C even if we separate them again. our earlier suspicion that heat transfer is averaging:
1
Tm = (Tm+1 + Tm−1 )
2
Mathematics The obvious extension to the two-dimensional case
is also valid:
The basic mathematical model for one- 1
dimensional heat transfer by conduction is a Tm,n = (Tm−1,n + Tm+1,n + Tm,n−1 + Tm,n+1 )
4
second-order partial differential equation:
There’s a lot to say about the limitations of finite
∂ 2T difference equations, but we won’t worry about
=0 that here. Also note that a full understanding of
∂x2
this math background is not necessary for complet-
Sometimes we can find analytical solutions to such ing the assignment!
equations, but often all we can do is approximate
the solution numerically. One approach to a nu-
merical approximation is to transform the differen-
1 Metal Rod [10 points]
tial equations into finite difference equations: Imagine a metal rod of negligible thickness stick-
ing through a building’s brick wall. The inside of
df f (x + ∆x) − f (x) the building is cooled to 70◦ F while the outside of
≈
dx ∆x the building is sweltering at 100◦ F . To keep things
simple we’ll assume that the wall itself has no in-
We discretize the continuous x domain into n fluence whatsoever on the temperature of the rod.
points p1 , p2 , . . . , pn spaced ∆x apart; pick some For this problem you will write a program that
point pm and approximate the first derivatives at computes the temperature distribution along the
pm − 21 ∆x and pm + 12 ∆x as follows: rod inside the wall. Please call your program
rod.py and nothing else. Figure 1 shows what
∂T Tm − Tm−1 the output of your program should look like: The
≈ temperature along the rod increases linearly from
∂x m−1/2
∆x
70◦ F inside the building to 100◦ F outside the
building.
∂T Tm+1 − Tm We need to decide two things to solve this prob-
≈
∂x m+1/2
∆x lem in Python: First, how should we represent
Figure 1 Correct temperatures along the rod. mathplotlib.pyplot over and over again we
abbreviate the imported module to a single capital
letter. For a larger program this convention of us-
ing a single capital letter may be confusing, but for
a rather short program we’ll be okay.
Finding the temperature of a segment inside the
rod requires that we average the temperatures of
its two neighboring segments. It’s important that
we average the two neighbors from the old temper-
ature distribution into a new temperature distribu-
tion. Otherwise we would average the values in-
consistently: one would be from the next step in
the simulation while the other would be from the
previous step. So we write a function that “runs
through” the current rod and computes a new tem-
perature distribution by averaging the relevant ele-
the temperature distribution of the rod? Second, ments of the old temperature distribution:
how should we perform the “repeated averaging” d e f distribute ( old ) :
of temperatures along the rod? new = old [ : ] # makes a copy
The temperature at any given point in the rod is a f o r i i n r a n g e ( 1 , LENGTH + 1 ) :
new [ i ] = ( old [ i − 1 ] + old [ i +←-
floating point number. Therefore the distribution of 1]) / 2
temperatures along the rod is a sequence of float- r e t u r n new
ing point numbers. The first and last temperatures
in this sequence are special because they represent Note that we only recompute the temperatures in-
the fixed temperatures inside and outside the build- side the rod, the boundary conditions never change.
ing.1 If we divide the rod itself into 100 equal-sized Clearly if we only call the distribute function once,
segments, the total length of our sequence is 102 only the temperatures at the ends of the rod will
elements including the fixed endpoints. So we can change. If we keep averaging, this process will
represent the rod with a vector of floats. The initial “grow” successive approximations to the final tem-
temperature of “unknown” interior segments could perature distribution from those endpoints toward
be set to anything, so let’s set those to 0.0 to be- the center of the rod.
gin with. Here is a first version of the program But when do we stop the computation? After
that simply sets up the initial temperature distribu- distributing the temperatures across the rod again
tion along the rod and plots it using pyplot from the and again, we will eventually reach a “steady state”
matplotlib library: in which none of the temperatures change signif-
i m p o r t matplotlib . pyplot as P
icantly anymore. So ideally we’d simply check
if the temperature distribution before averaging is
LENGTH = 100 equal to the temperature distribution after averag-
ing. However, since our temperatures are floats we
d e f main ( ) : cannot compare for “exact” equality. Instead we
current = [ 0 . 0 ] ∗ ( LENGTH + 2 )
have to figure out if two floating point values are
current [ 0 ] = 7 0 . 0
current[ −1] = 1 0 0 . 0 “close enough” so we can consider them “equiva-
lent” as far as our simulation is concerned. Let’s
P . plot ( current ) write a function that performs this “approximate
P . xlabel ( ” s e g m e n t ” ) equality check” given a bound (epsilon); we’ll
P . ylabel ( ” t e m p e r a t u r e ” ) use the absolute error version here (for other ap-
P . show ( )
plications the relative error version may be more
main ( ) appropriate):
Note the new form of import we’ve used here. 1. In the language of partial differential equations, these
Since it would be rather tedious to keep writing two temperatures are our boundary conditions.
P . plot ( current )
P . xlabel ( ” s e g m e n t ” )
2 Metal Plate [22 points]
P . ylabel ( ” t e m p e r a t u r e ” )
P . show ( ) Imagine a metal plate of negligible thickness that
we apply heat sources or cooling agents to. Fig-
Starting from the initial temperature distribution, ure 3 shows what happens to a plate that started
we keep averaging until two consecutive distribu- out at 25◦ C when we apply a single heat source of
tions are close enough. Once we reach that “steady 100◦ C: Eventually the entire plate will have a tem-
state” we plot the final temperature distribution. perature of 100◦ C. Figure 4 shows a slightly more
Done! interesting scenario: If the plate is surrounded by
Except for one thing: We never actually a cooling agent that keeps the edges at 25◦ C, then
said what should be for this simulation! only a relatively small part of the plate will actu-
Play with different values for (for example ally get warm. Figures 5 and 6 illustrate how heat
Figure 3 One heat source, no boundary conditions. Figure 6 One heat source, one boundary cooled.
Figure 4 One heat source, all boundaries cooled. Figure 7 Sample input to the plate.py program.
10
CCCCCCCCCC
.........C
.........C
.........C
.........C
.........C
.........C
..H......C
.........C
.........C
riod “.” simply means that the temperature of that >>> new_matrix(3, 1, False)
cell is not fixed but computed as part of the simu- [[False], [False], [False]]
lation; it’s probably easiest if you set those cells to >>> numRows([[1], [2]])
2
0◦ C initially. >>> numCols([[1], [2]])
Conceptually the program you need to write for 1
this problem is very similar to the one we wrote
for the metal rod before: Once again you’ll have to You can reuse floats_equiv(a, b) from
set up a representation for the temperature distribu- the previous problem, but you’ll have to write
tion, once again you’ll have to average that distri- a new matrices_equiv(a, b) function for
bution repeatedly until you reach a “steady state,” this one.
and once again you’ll plot the final temperature One matrix for the temperature distribution is
distribution. Nevertheless, the program for metal not enough however. You’ll also have to somehow
plates is significantly more complicated, so think- keep track of which positions in your matrix are
ing things through ahead of time is a very good fixed by boundary conditions and which positions
idea. are free in the sense that you need to compute their
Let’s talk about the representation of the temper- temperature. We suggest that you use two matri-
ature distribution first. Obviously you cannot use ces. The first one (called current in the sam-
a one-dimensional representation anymore, instead ple listing below) is a matrix of floating point val-
you’ll have to use a matrix—i.e. a list of nested ues and contains the temperatures in each “cell”
lists—that you can index with a row and a col- of the metal plate we’re simulating; this includes
umn. We have written some code for matrices be- the temperatures for boundary conditions as well.
fore, and if you have it in your notes or download The second one (called fixed in the sample list-
it from the course schedule, you can put it to good ing below) is a matrix of boolean values, True and
use here. Your choices for using it are to cut and False; an entry in this matrix is True if it’s tem-
paste the necessary functions into this program file, perature is specified by a boundary condition and
or to put our matrices.py module in the same folder therefore fixed; an entry in this matrix is False if
as these programs and import it into your plate.py the corresponding temperature cell is one you have
program. If you do it the second way, make sure to compute.
that you include matrices.py in your submission With this in mind, here is a possible main pro-
zip! gram that illustrates how closely related the “big
You’ll definitely need a function to create a new picture” of the metal rod and the metal plate are:
matrix of a given size as it would be tedious to re- d e f main ( ) :
peat the code for that every time you need a new current , fixed = read_config ( ”←-
matrix. A function like plate . txt ”)
on the right side that explains which color corre- 3 NumPy Heat Transfer [18
sponds to which temperature.
points]
The new distribute function needs both the
temperature distribution current and the ma- For this problem you’ll re-write the program for
trix fixed telling it which cells are “not to be the heat-plate simulation, but this time using the
touched” as it were. So when you’re about to com- N UM P Y library for scientific computations. That
pute the average for a given cell, you check first is, your new program should produce the exact
if that cell is a boundary condition; if so, you skip same results as the previous program (as far as pos-
it; otherwise you actually compute its new aver- sible with floating point numbers), but it should
age. Computing the average for a given cell re- be faster because N UM P Y allows you to move
quires that you get the temperatures of the 2–4 many computations from P YTHON down closer
neighboring cells (north, south, east and west); this to the machine itself. Please call your program
is more complicated than in the case of the metal plateFast.py, nothing else.
rod, and you probably don’t want to do it in the The last step of the program to draw the resulting
distribute function itself; instead you should steady-state heat distribution using matplotlib
write a helper function neighbors that returns a will be exactly the same in this version of the pro-
list with 2–4 temperatures depending on the posi- gram. The part of the program that sets up the tem-
tion you pass in (think about “cells” at the “edge” perature and fixed matrices from an input file will
of the plate). be enhanced to create a third matrix necessary to
What’s left is the read config function perform the revised computation as described be-
which has to read the data file and create both of low. Also, the function to determine if two matri-
the matrices. After opening the file you should read ces are equivalent should be rewritten to take ad-
a single line first to find out how big you need to vantage of N UM P Y’s fast operations.
make your matrices and to know how many more However, the main difference will be in the part
lines to expect: of the program that repeatedly averages the tem-
peratures around each cell to compute the new tem-
perature of a cell using something like this equa-
data = open(name) tion:
line = data.readline() 1
Ti,j = (Ti−1,j + Ti+1,j + Ti,j−1 + Ti,j+1 )
size = int(line) 4
... At the edges and corners of the plate where we have
only 2 or 3 neighbors instead of 4, the formula is
adjusted accordingly. This is the part we’ll tackle
After that step you just read line-by-line as we’ve rewriting first.
always done it; for each line, you have to go This part of the program takes the most time:
through the characters on that line and initialize in- We repeatedly “run across” the rows and columns
dividual elements of your matrices as appropriate. of the plate to produce new values for all com-
Add any other helpful functions you may think of puted cells, and all of the computation is carried
to simplify the coding in this one. Note that the out “cell-by-cell.” So only the actual arithmetic is
way we’ve written and used this function, it returns performed directly by the machine (fast on aver-
a tuple of both matrices! age), most of the remaining code (loops, access of
Lastly, update main() so that it prompts the user matrix elements) is performed by P YTHON (slow
for the name of the input file. That way you can on average). Therefore this is the part where we
easily run your program with many different plate can improve performance the most if we are able
configurations. We’ve created a starter file for you to express our solution in terms of matrix opera-
to use, plateStart.py, which has the skeleton tions provided by N UM P Y: each of those opera-
of each method described here, along with some tions is fast (on average) and deals with all cells of
documentation and a few of the harder new state- the matrix on the machine level without involving
ments as well. Don’t forget docstrings, doctests P YTHON again.
and pep8 style for all parts of your finished pro- We need to rethink the way we perform averag-
gram! ing to get away from the idea of doing it “cell-by-
cell”, a relatively slow sequential approach. In- Figure 8 The numpy.roll function in action.
stead of dealing with a single cell and it’s neigh- >>> a
array([[1, 2, 3],
bors, we want to deal with all cells at the same
[4, 5, 6],
time, a faster parallel processing approach. Obvi- [7, 8, 9]])
ously we have to still worry about the edges and >>> numpy.roll(a, 1, 0)
corners of the plate where the averaging computa- array([[7, 8, 9],
tion needs to proceed differently than in the center, [1, 2, 3],
but let’s focus on the center first. [4, 5, 6]])
>>> numpy.roll(a, -1, 0)
If you consider a cell (i, j) for which we want array([[4, 5, 6],
to compute a new temperature, we first need to [7, 8, 9],
sum up the temperatures of the cells around it: [1, 2, 3]])
(i − 1, j), (i + 1, j), (i, j − 1), (i, j + 1). The key in- >>> numpy.roll(a, 1, 1)
sight is to realize that instead of looking at the left array([[3, 1, 2],
[6, 4, 5],
(i, j − 1) neighbor, we could simply “shift the ma-
[9, 7, 8]])
trix” one column to the right and use the cell (i, j) >>> numpy.roll(a, -1, 1)
of the shifted matrix. Of course the same is true array([[2, 3, 1],
for the other neighbors: Each of which can equally [5, 6, 4],
well be found at position (i, j) in some appropri- [8, 9, 7]])
ately shifted matrix. As soon as we “line up” the
elements correctly, we can use N UM P Y’s element-
wise whole matrix addition operation to express the trix elements around. Once we roll, we need to
averaging step as follows: zero out the new row or column, and we can use
N UM P Y’s convenient array slicing operations for
1
N = ↑ O+↓ O+← O+→ O that. Here is one of the resulting functions - it’s up
4
to you to write the other three.
Here N is the “new” matrix of (averaged) tem- d e f up ( matrix ) :
peratures while O is the “old” matrix of temper- ” ” ” Move m a t r i x up one row . ” ” ”
new = N . roll ( matrix , −1, 0 )
atures. Obviously this is not yet correct for edges
new[ −1 , : ] = 0
and corners: First we should only add 2 or 3 cells r e t u r n new
there instead of 4, second we should divide by 2 or
3 instead of 4 to get the correct average. d e f down ( matrix ) :
Let’s focus on how we would shift the ma- ” ” ” Move m a t r i x down one row . ” ” ”
trix first; luckily N UM P Y provides a function pass
roll that works as demonstrated in Figure 8:
d e f left ( matrix ) :
We can “shift” a matrix by a certain number ” ” ” Move m a t r i x l e f t one column . ” ” ”
of elements along a certain axis; for example, pass
numpy.roll(a, 1, 0) shifts all rows down
by one. In general, for a multi-dimensional array, d e f right ( matrix ) :
the roll function takes an array as the first parame- ” ” ” Move m a t r i x r i g h t one column . ←-
”””
ter, the shift amount as the second parameter, and pass
the dimension or axis along which to shift as the
third parameter. In the first example, note that the Using these functions, we can shift our matrix
row “shifted out” at the bottom re-enters the matrix appropriately and add the shifted matrices together
at the top. to compute the sums we need in each new cell: for
That last aspect is what makes roll not quite cells in the center of the matrix, the four shifted
suitable for us: The only way to “ignore” a neigh- matrices will have the correct neighbors at position
bor when averaging (as we need to along the edges) (i, j); for cells on the edges of the matrix, three
is to fill in a row or column of zeros instead! The of the four shifted matrices will have the correct
functions you need to shift the temperature matrix neighbors while one will have a zero; for cells in
for averaging will have to do that, but we can still the corners of the matrix, two of the four shifted
use roll to do the expensive part of moving ma- matrices will have the correct neighbors while two
Figure 9 The numpy.where function in action. ready used such a matrix to keep track of where
>>> a the boundary conditions are, but again you had to
array([[False, True, True],
process it one element at a time; now we can use
[ True, False, True],
[ True, True, False]], dtype=bool) N UM P Y to distinguish between boundary condi-
>>> b tions and computed cells in one go. So provided
array([[ 4., 4., 4.], we have three matrices, one with the current tem-
[ 4., 4., 4.], peratures (old), one with the boundary conditions
[ 4., 4., 4.]]) marked by True (clamped), and one with the ap-
>>> c
array([[ 0., 0., 0.],
propriate divisors (divs), our averaging step now
[ 0., 0., 0.], reads as follows:
[ 0., 0., 0.]]) d e f average ( old , clamped , divs ) :
>>> where(a, b, c) sums = up ( old ) + down ( old ) + left ( ←-
array([[ 0., 4., 4.], old ) + right ( old )
[ 4., 0., 4.], averages = sums / divs
[ 4., 4., 0.]]) new = N . where ( clamped , old , ←-
averages )
r e t u r n new
will have zeros. However, to compute the averages That was simple, wasn’t it? No more neigh-
we still have to divide by the appropriate number bors to find, no more values to add up and average,
of neighbors. Luckily N UM P Y allows us to per- it’s all taken care of by N UM P Y. The rest of the
form element-wise division of a matrix, so if we program is mostly concerned with input and out-
set up a corresponding divisors matrix of the same put just like before. However, you must use the
size as the temperature matrics, with values 2 in the N UM P Y zeros function to create the initial matri-
corners, 3 on the edges, and 4 everywhere else, we ces. A N UM P Y matrix is not the same data type as
can divide the matrix of sums by the divisors ma- a nested list - so all our fast operations and revised
trix to compute the proper averages. Building this functions will only work if we consistently use the
matrix is pretty straightfoward, using the functions same data type throughout the program. Lastly,
we’ve already built: we will also compare matrices using N UM P Y of
d e f make_divisor ( size ) : course. This can be done very compactly with ma-
base = N . ones ( ( size , size ) ) trix subtraction and the min and max functions, as
div = up ( base ) + down ( base ) + left←- shown in the listing below.
( base ) + right ( base )
r e t u r n div i m p o r t matplotlib . pyplot as P
i m p o r t numpy as N
Note that you don’t want to use the old cell-by-cell
method of initializing a matrix so that you have the EPSILON = 0 . 0 0 0 1
HOT = 1 0 0 . 0
most speed-up possible. COLD = 2 5 . 0
There’s one more thing to take care of though:
Our approach of averaging the entire matrix also ...
computes results for positions that should not be
changed because they are boundary conditions! In d e f matrices_close_enough ( a , b ) :
d = a − b
other words, once we are done computing the av-
r e t u r n max ( a b s ( d . min ( ) ) , a b s ( d . max←-
erages, we want to write only those into the re- ( ) ) ) <= EPSILON
sult matrix that were not already fixed in the initial
problem. This is where N UM P Y’s where func- Let’s not forget the reason we did all this: so
tion comes in handy, see Figure 9: We can select our program would run faster! You’ll need to use
between the elements of two matrices based on a a pretty large input file in order to see a notice-
third bool matrix; if, for example, all elements able difference between the original plate.py solu-
of cond are True, then numpy.where(cond, tion and the new plateFast.py solution. For an extra
a, b) is just going to be the matrix a; if they are challenge, write a program to randomly create arbi-
all false, it’s going to be the matrix b instead. trarily large input files that simulate CPU cooling.
In the solution to part two you should have al- Submission Summary: Remember to include