AustinTek homepage | Linux Virtual Server Links | AZ_PROJ map server |

Material leftover from the Introductory Course

Joseph Mack

jmack (at) wm7d (dot) net
AustinTek home page

v20100219, released under GPL-v3.

Abstract

I didn't use all the material written for the Introductory Course before we changed to teaching programming in C. Here it is. Presumably some of this material will migrate to the C course.

Material/images from this webpage may be used, as long as credit is given to the author, and the url of this webpage is included as a reference.


Table of Contents

1. e - the base of natural logarithms: exponential and geometric processes
1.1. Calculating Bank Interest at small intervals
1.2. Errors in the calculation of e=(1+1/n)n
1.3. Logarithmic time for calculating e
1.4. Faster converging series for e: factorial series
1.5. Errors in the calculation of e from the factorial series, Arithmetic Progressions and the number of casing stones on the Great Pyramid
1.6. Logarithms and Slide Rules
1.7. The Great Numbers of Mathematics
1.8. Compound Interest
1.9. Mortgage Calculation
1.10. Refinancing a Mortgage
1.11. Moore's Law: effect on calculation of e=(1+1/n)^n
1.12. Optimal Slacking and the Futility of Interstellar Space Travel
1.13. Geometric Progressions: Summing the series
1.14. Geometric Progressions: chess and the height of a pile of rice
1.15. Geometric Progressions: Time for the Moore's Law aided calculation of e=(1+1/n)n
1.16. Geometric Progressions: creating money by multiplication
1.17. Parallel Programming and Distributed Denial of Service (DDoS): linear or exponential?
1.18. Lucky Lindy
1.19. Getting in early when you have an exponential process
1.20. Presentation: e, exponential and geometric series
2. Nim (the subtraction game)
2.1. Nim: Rules
2.2. Nim: Strategery
2.3. Nim: a working but fragile version
2.4. Nim: Handling User Input
2.5. Nim: better user interface: storing the game history
2.6. Nim: better user interface: showing the counters
2.7. Nim: better user interface: appending plays
2.8. Nim: better user interface: appending plays by reversing the list
2.9. Nim: better user interface: appending plays by popping the list
2.10. Nim: better user interface: Legend line
2.11. Nim: Imperfect opponent
3. Top Level Down (top down), Bottom Level Up (bottom up) Design
4. Prime Numbers: The Sieve of Eratosthenes
5. Perfect Numbers
5.1. Perfect Numbers: order of the algorithm
5.2. Perfect Numbers: run once
6. Cryptography
7. Recursion
8. Projects
8.1.
8.2.
8.3.
8.4.
8.5.
9. Examples
10. Back to basics: Base 256
11. Some Trigonometry
11.1. sin(),cos(), tan(), radians
11.2. Angular diameter of the sun and moon; annular eclipses
11.3. Angles of the Great Pyramid
11.4. Angles in other Pyramids
12. Elementary Probability
12.1. One Coin Toss
12.2. Two Coin Toss

1. e - the base of natural logarithms: exponential and geometric processes

e≍2.71828 is the basis of equations governing exponential growth, probability and random processes, and wave functions. It one of the great numbers in mathematics. e winds up in all sorts of places.

1.1. Calculating Bank Interest at small intervals

Let's say you deposit $1 in an interest bearing bank account, where your interest is calculated annually.

etymology of bank: Bankers (banchieri) were originally foreign money exchangers, who sat at benches (stall would be a better word; the french word for stool is "banc"), in the street, in Florence. (from The Ascent of Money, Niall Ferguson, The Penguin Press, NY, 2008, p42. ISBN-978-1-59420-192-9) (Italy was the birth place of banking.) The word "bank" is related to "embankment".

Let's assume your annual interest rate is 100%, calculated annually. At the end of the first year, the balance in your account will be $2. What would you guess would be the balance in your account, if your interest was calculated semi-annually (i.e. 50% interest twice a year), or quarterly (25% interest, but now 4 times a year), or monthly at 8&1/3%? How about weekly, daily, hourly, by the nanosecond? You'd be getting a smaller amount of interest each time, but the number of times your interest is added would be increasing by the same ratio. At the end of the year would you be

  • worse off
  • the same
  • better off

than if interest were calculated annually?

Let's try interest calculated twice a year. At the end of the first 6 months, your interest will be $1*1/2=$0.50 and your balance will be $1*(1.0+0.5)=$1.50. At the end of the year, your balance will be $1.50*(1.0+0.5)=$2.25 (instead of $2.00). It appears to be to your advantage to calculate interest at shorter intervals.

Will your balance at the end of the year be finite or infinite? Some series when summed go to 0, some reach a finite sum and some go to ∞. Unless you know the answer already, you aren't likely to guess the sum to ∞ of anything.

Let's find out what happens if you calculate interest at smaller and smaller intervals. Write code (filename calculate_interest.py) that has

  • annual_interest=1.0
  • initial_deposit=1.0
  • evaluations_per_year=1

Using these variables, write code to calculate (and print out) the value of balance at the end of the year For the first attempt at writing code, calculate interest only once, at the end of the year. Here's my code [1] and here's my output

igloo:# ./calculate_interest.py
evaluations/year 1 balance is 2.000000

Modify the code so that interest is calculated semi-annually (i.e. do two calculations, one after another) and print out the balance at the end of the year. Here's my code [2] and here's the output

igloo:# ./calculate_interest.py
evaluations/year 2 balance is 2.250000

If you haven't already picked it, the interest calculation should be done in a loop. Rewrite the code to interate evaluations_per_year times. Have the loop print out the loop variable and the balance. (If you're like me, you'll run into the fencepost problem and the number of iterations will be off by one; you'll have adjust the loop parameters to get the loop to execute the right number of times.) Here's my code [3] and here's the output

igloo:# ./calculate_interest.py
1 balance is 1.500000
2 balance is 2.250000
evaluations/year 2 balance is 2.250000

Try the program for interest calculated monthly and daily. Turn off the print statement in the loop and put the loop inside another loop, to calculate the interest for 1,10,100...108 evaluations_per_year, printing out the balance for each iteration of the outer loop.

Here's the code [4] and here's the output

igloo:# ./calculate_interest.py 
evaluations/year          1 balance is 2.0000000000
evaluations/year          2 balance is 2.2500000000
evaluations/year         10 balance is 2.5937424601
evaluations/year        100 balance is 2.7048138294
evaluations/year       1000 balance is 2.7169239322
evaluations/year      10000 balance is 2.7181459268
evaluations/year     100000 balance is 2.7182682372
evaluations/year    1000000 balance is 2.7182804691
evaluations/year   10000000 balance is 2.7182816941
evaluations/year  100000000 balance is 2.7182817983
evaluations/year 1000000000 balance is 2.7182820520
evaluations/year 2000000000 balance is 2.7182820527

Since we haven't calculated an upper or lower bound, we don't have much an idea of the final value. It could be 2.71828... or we could be calculating a slowly divergent series, which gives a final answer of ∞. It turns out we're calculating the value of e, the base of natural logarithms. One of the formulas for ex is (1+x/n)n for n→∞. The final value then is finite; the best guess, as judged by unchanging digits is 2.718282052. Here's the value of e from bc (run the command yourself, to see the relative speed of bc and the calculation we ran).

igloo:~# echo "scale=100;e(1)" | bc -l
2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274

2.718282052 for comparison, the value from (1+1/n)^n
Note
almost half our digits are wrong, we'll see why later.

As for the earlier calculation of π using bc, check that the value of e is not stored inside bc by timing the calculation of e1.01.

For your upcoming presentation on e, make a table of the balance at the end of the year in your account if interest is calculated annually, monthly, weekly, daily, by the hour, minute, second.

1.2. Errors in the calculation of e=(1+1/n)n

This method of calculating e gets about half a decimal place, for a factor of 10 increase in calculation time. This is painfully slow, but if there wasn't anything better (there is), you'd have to use it.

When you're doing 109 (≅232) arithmetic operations (e.g. addition, multiplication), you need to know whether the rounding errors have overwhelmed your calculation. In the case of pi errors we got a usable result, because the errors were relative to the size of the numbers being calculated and by going to higher precision (e.g. to 64 bit).

What happens with this calculation of e? We're calculating with (1+1/n)n, where n is large. If we were using 32 bit reals, i.e. with a 24 bit mantissa), we would chose the largest n possible, n=224 giving a mantissa of 1.0000000000000000000000012. Because of rounding, the last two digits could be 00,01 or 10. The resulting number then would be e0=1, e1≍2.7 or e2≍7.4. We're calculating using a number that is as close to 1.0 as possible and the magnitude of the rounding errors is comparable to the magnitude of the last few digits in the number being multiplied. We will have large errors, because the calculation is dependant on the small difference from 1.0, rather than the relative value as it was in the case of the calculation of π.

At this stage let's look at the machine's epsilon and then return here.

Here's the value for e calculated from the series and the real value from bc.

e from interest series  e=2.718282052
e from bc               e=2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274

Our calculation is wrong in the last 4 digits. We need a different method of calculating e.

1.3. Logarithmic time for calculating e

Let's say we wanted to calculate e=(1+1/n)n for n=1024. In the above section, we did this with 1024 multiplications. Instead let's do this

x=(1+1/1024)
x = x^2		# x=(1+1/1024)^2
x = x^2		# x=(1+1/1024)^4
x = x^2		# x=(1+1/1024)^8
x = x^2		# x=(1+1/1024)^16
x = x^2		# x=(1+1/1024)^32
x = x^2		# x=(1+1/1024)^64
x = x^2		# x=(1+1/1024)^128
x = x^2		# x=(1+1/1024)^256
x = x^2		# x=(1+1/1024)^512
x = x^2		# x=(1+1/1024)^1024

Doing it this way, we only need 10 iterations rather than 1024 iterations to find (1+1/1024)1024.

Note
Note that 1024=210 (i.e. log21024 = 10) and we needed 10 steps to do the calculation.

This algorithm scales with O(log(n)) (here 10) and is much faster than doing the calculation with an algorithm that scales with O(n) (here 1024). You can only use this algorithm for n as a power of 2. This is not a practical restriction; you usually want n as big as you can get; and a large power of 2 is just fine.

The errors are a lot better. The divisor n is a power of 2 and has an exact representation in binary. Thus there will be no rounding errors in (1+1/n). As with the previous algorithm, we still have the problem when 1/n is less than the the machine's epsilon (what's the problem [5] ?). There still will be a few rounding errors. Look what happens when you square a number close to (1+epsilon) (assume an 8-bit machine)

(1+x)^2=1+2x+x^2
for x=1/2^8
then 
2x=1/2^7
x^2=1/2^16

While addition (usually) will fit into the register size of the machine (unless you get overflow), multiplication always requires twice the register width (if you have a 8-bit machine, and you want to square 255, how many bits will you need for your answer?). To do multiplication, the machine's registers are double width. (Joe FIXME, is this true for integers?) For integer multiplication, you must make sure the answer will fit back into a normal register width, or your program will exit with an overflow error. For real numbers, the least significant digits are dropped. How does this affect the squaring in an an 8-bit machine? Here's the output from bc doing the calculations to 40 decimal (about 128 bit) precision. The 8-bit computer will throw away anything past 8-bits.

x=1+1/2^8=10000001 binary
x^2      =1000001000000001
x^4      =10000100

 1.0000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 1.0000010000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 1.0000100000011000001000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 1.0001000001110001110001000110011100000111000001000000000011111111111111111111111111111111111111111111111111111111111111111111111111111
 1.0010000111110001111100111110100111101000100010101001111111011101101010001111001000000110000111100000100000000000111111111111111111101
 1.0100100001100100001011010110001101000101110101101011011100100011110000011101101111100110100010101101001111001111011000011000110100010
 1.1010010101000000110110111000000111100000100100001101001000010111010010000011010010011101001011110001001110100110110010111110011110111
10.1011010100101110011000100110011110101001110001000001001110000101011111011000011001111001111000011111110100011000111001011111011011011


We should be able to get However in the example above, we don't have to multiply the error 1024 times, only 10 times. With this algorithm, we should expect an answer with smaller errors in a shorter time.

If errors weren't a problem, how many iterations would we need (approximately) if we were to calculate e=(1+1/n)n, using the O(logn) algorithm, for n=109 [6] ? Write out each step by hand to see that you get this answer. If we scale to multiplying 230≅109 times, we'll only multiply the rounding errors 30 times.

What is the maximum number of squarings we can do on a 64-bit machine (hint: x=(1+1/n) can't be less than (1+epsilon). What value of n will do this?) [7] ?

Could we have used the logarithmic algorithm for the numerical integration of π [8] ?

Let's code this up. Copy calculate_interest.py to calculate_interest_logarithmic.py. (You're going to gut most of the old code.) Here's the first attempt at the writing the code (you're doing only one iteration, so there's no loop yet).

  • change the documentation to reflect the new name, and function
  • initialise the following variables
    • initial_deposit=1.0
    • annual_interest=1.0
    • iterations=1
    • evaluations_per_year=2**iterations
    • x=1+annual_interest/evaluations_per_year
    • iteration=1
  • print the iteration and the balance (x * initial_deposit)

Here's my code [9] and here's my output.

pip:/src/da/python_class/class_code# ./!$
./calculate_interest_logarithmic_1.py
evaluations/year          1
iteration   1 balance=2.00000000000000000000

We now need to add a loop to do the squaring operations. Note in the initial example above (with 10 iterations), that the first iteration did something different to the rest of the iterations (the first iteration gave the formula for calculating x while the rest of the iterations did the squaring operations). We could implement this in either of these two logically equivalent ways.

#pseudo code
iterations=10

#iteration #1
define x=(1+1/n)
print statement

#iterate for loop_variable =2,iterations+1
	x=x**2
	print statment

or

#pseudo code
iterations=10

iterate for loop_variable=1,iterations+1
	if loop_variable==1
		define x=(1+1/n)
	else
		x=x^2

Both of these are a bit messy.

  • In the first method, the loop does only one type of calculation (the squaring) (which is easy to read), but the loop variable starts at 2, which is a little difficult to read. As well you need print statements in two places.
  • In the 2nd method, the loop has to do an if/else, when the if only succeeds on the first iteration (i.e. it's only needed once). Since you won't be running this loop more than 52 times, you don't need to optimise the loop code, so an if/else isn't particularly bad. If you were iterating 106 times, then you wouldn't want the if/else inside the loop.

Sometimes you can't write aesthetically pleasing code; sometimes it's a mess. A problem which is trivial for a human to do in their head, can be quite hard to write down. You can write code to do it either of these two ways (or any other way you want). I'll show both methods above.

For the 1st method: below your current code,

  • add a loop that allows the code to evaluate (1+1/n) a total of iterations times, each time squaring x (you have the fencepost problem: allow yourself to get the number of iterations wrong initially and fix up the number of iterations later). Use the variable iteration as the loop variable. Because you've already done one calculation before you enter the loop, make sure you output the correct number of the iteration.
  • At each iteration print out the number of times you've evaluated x and the balance in the account for the current value of x (the only value of x that the customer cares about is the value when the loop exits).
  • When the loop exits, print out the balance (the value of e).

Here's my code for (1st method) [10] and here's my output for iterations=1

and for iterations=4

For the 2nd method:

  • create a loop with loop variable iteration
  • inside the loop, when iteration==1, define x=1+annual_interest/evaluations_per_year
  • else square x
  • for each iteration, print the iteration and the balance
  • after the loop exits, do the same

Here's my code (2nd method) [11] and here's my output for iterations=1

and for iterations=4

1.4. Faster converging series for e: factorial series

The definition of a factorial is

n!=n*(n-1)*(n-2)...1

where n! is pronounced "n factorial" or "factorial n"

examples
3!=3*2*1=6
6!=6*5*4*3*2*1=120

Factorial series increase the fastest of all series (even faster than exponential series). Factorials wind up in combinatorics.

How many different orderings, left to right, are possible if you want to line up 4 people in a photo? The answer is 4!. You can pick any of 4 people for the left most position. Now you have 3 people you can chose from for the next position, then 2 for the next position, and there is only 1 person for the last position. Since the choices of people at each position are independant, the number of possibilities is 4*3*2*1=4!.

Note
0!=1. (there is only 1 way of arranging 0 people in a photo with 0 people.)

Here's a faster converging series to calculate e:

e=1/0!+1/1!+1/2!+1/3!...

Since you'll need a conditional to handle calculation of factorial n for n=0, it's simpler to use the series

e=1+1/1!+1/2!+1/3!...

and have your code initialise e=1, allowing the loop for the factorial series to start at 1/1!.

Write code (e_by_factorial.py) to

  • initialise e=1
  • initialise factorial_n=1.0

Now you want to calculate the value of e from the first two terms (1+1/1!). Do this by

  • calculating the first factorial term (1/1!) from the initial value of factorial_n, putting the result into factorial_n.
  • add factorial_n to e

then prints out e. Here's my code [12] and here's my output.

pip:# ./!$
./e_by_factorial.py
2.0

What does the construction "/=" do?

Now put the calculation of e into a loop, which calculates the next factorial number in the series and adds it to e. Iterate 20 times, each time print out the calculated value for e using

	print "%2.30f" %e

Here's my code [13] and here's my output

dennis:/src/da/python_class/class_code# ./e_by_factorial.py
2.000000000000000000000000000000
2.500000000000000000000000000000
2.666666666666666518636930049979
2.708333333333333037273860099958
2.716666666666666341001246109954
2.718055555555555447000415369985
2.718253968253968366752815200016
2.718278769841270037233016410028
2.718281525573192247691167722223
2.718281801146384513145903838449
2.718281826198492900914516212652
2.718281828286168710917536373017
2.718281828446759362805096316151
2.718281828458230187095523433527
2.718281828458994908714885241352
2.718281828459042870349549048115
2.718281828459045534884808148490
2.718281828459045534884808148490
2.718281828459045534884808148490

Note that the output increases in precision by a factor of 10 for each iteration (at least up to iteration 15 or so). This is a quickly converging series (all series based on factorials converge quickly). Although you can't tell from the output, this series does converge. Note that the last 3 entries are identical and match the 4th last entry to 15 decimal places.. What piece of information brings together "real" and "accurate to 15 decimal places" [14] ? As a check, compare the output of your program with the value from bc.

e from bc               e=2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274
e_by_factorial          e=2.718281828459045
e from interest series  e=2.71828

The value from the factorial series matches the real value to 16 places.

1.5. Errors in the calculation of e from the factorial series, Arithmetic Progressions and the number of casing stones on the Great Pyramid

It's possible that any real arithmetic operation (i.e. +-*/) will be rounded, so you must plan for an upper bound in errors of 1 bit/operation. With the 52 bit mantissa for 64 bit math, each operation can potentially introduce an error of 1:252 (≅ 1:1016 - the machine's epsilon). We needed 109 operations to calculate e by the previous method. How many operations were needed to calculate e by the factorial series? We needed only 16 iterations to calculate e to the precision of python's 64 bit math. How many real math operations were involved in the 16 iterations? It's obviously very much less than 109, but we'll need the exact number in the future, when we start comparing two good (fast) algorithms, so let's calculate it exactly.

The addition of a term in the formula for e requires

  • the calculation of the factorial (n multiplications)
  • a division
  • an addition

This is n+2 operations. For a back of the envelope calculation, let's assume that n operations are required (we can come back and do the exact calculation later). For 16 iterations then, the number of operations is 1+2+.....16. This series of numbers; 1,2..16, is an arithmetic progression

A series of numbers, where the difference between consecutive numbers is constant, is called an Arithmetic Progression (http://en.wikipedia.org/wiki/Arithmetic_progression). As an example, the set of numbers 2,5,8,11....29, is an arithmetic progression, whose first and last members are 2,29 with a difference of 3.

First we can find an approximate upper bound for the number of operations. Assume the series was 16,16...16 for 16 numbers. The sum of these numbers is 256 giving an upper bound of 256 for the number of operations to calculate e to 16 significant figures. Let's get a better estimate. Here's a diagram showing the number of operations for each term, the first where the number of operations is modelled at 16 for each term, and the second where the number of operations is modelled as n for each term. The sum of the heights of all the members of the series can be found from the area of each of the two diagrams.

****************                 *
****************                **
****************               ***
****************              ****
****************             *****
****************            ******
****************           *******
****************          ********
****************         *********
****************        **********
****************       ***********
****************      ************
****************     *************
****************    **************
****************   ***************
****************  ****************
height=16         height=1..16

The first is a square 16*16 (it doesn't look square because the area occupied by a letter on a computer screen is a rectangle), and the second triangular looking object of 16*16. The second diagram represents the actual number of operations for the 16 iterations used to calculate e. Using simple geometry, what is the number of operations (the area of the 2nd diagram) [15] ?

Where's the fencepost error? The triangular looking object above is not a triangle at all; it's a set of stairs. The "*" characters on the hypoteneuse are full characters, and not a "*" sliced diagnonally along the hypotenuse. Here's what the object really looks like

	       _|*
	      _|**
	     _|***
            _|****
           _|*****
          _|******
         _|*******
        _|********
       _|*********
      _|**********
     _|***********
    _|************
   _|*************
  _|**************
 _|***************
 |****************
height=1..16

To calculate the area geometrically, we duplicate the object, rotate it 180°, and join it with the original to make a rectangle (here the duplicated version uses a different symbol).

................
...............*
..............**
.............***
............****
...........*****
..........******
.........*******
........********
.......*********
......**********
.....***********
....************
...*************
..**************
.***************
****************
height=1..16

What is the area of this rectangle and hence the number of real operations required to calculate e [16] ?

This means that upto 7 bits (approximately, it would be 7 bits if 128 operations were used) of the 52 bit mantissa could be wrong, or 7/3 i.e. the last 2 decimal digits could be wrong. From the output from bc above, we don't see any rounding errors.

From your understanding of the construction of the rectangle above, what's the formula for the sum of the elements of an arithmetic progression, which has n elements, the first term having the value first and the last term having the value last [17] ?

The number of operations was n+2 and not n. Now let's do the exact calculation. The number of operations is 3..18. Here's the diagram showing the number of operations at each iteration of the loop.

................
................
................
...............*
..............**
.............***
............****
...........*****
..........******
.........*******
........********
.......*********
......**********
.....***********
....************
...*************
..**************
.***************
****************
****************
****************
height=3..18

What's the number of real number operations (do it by inspecting the diagram here and from your formula). [18] ? Does the exact calculation make any real difference to the number of bits of the mantissa that could be wrong [19] ?

The factorial series is better for calculating e than the (1+1/n)n series for two linked reasons

  • it converges faster (factorial series converge the fastest)
  • the smaller number of calculations result in less rounding errors.

There's an interesting story about arithmetic progressions and one of the great mathematicians, Gauss. When Gauss was in grade school, the teacher wanted to keep the students busy for a while, so they wouldn't bother him, and he set the class to the task of adding all the numbers 1..100. Gauss handed in his answer immediately, while the rest of the class swatted away, presumably adding all the numbers by brute force. The teacher thought Gauss was being insolent and didn't look at Gauss' answer till all the other student's answers had been handed in and marked. The teacher was astonished to find that Gauss' answer was correct.

Before we look at how Gauss did it, let's look at addition of all numbers from 1..99 (we can add the 100 later). People in this class familiar with loops and finding common elements in a problem, should be able to see that addition of (1+2+3+4+5+6+7+8+9)+(10+11+12+13+1+4+15+16+17+18+19)..99 involves a core problem of adding the numbers 1..9 a couple of different ways. Can you see how to add 1..99 [20] ?

Gauss used the geometric constuct we used above, although he didn't need to draw the dots as we did. Gauss wrote out the numbers in a line and then below them wrote out the numbers in the reverse order. His notebook looked like this

  1   2   3   4 ...  97  98  99  100
100  99  98  97 ...   4   3   2    1

What are the sums of each pair of terms?

  1   2   3   4 ...  97  98  99  100
100  99  98  97 ...   4   3   2    1
--- --- --- ---     --- --- ---  ---
101 101 101 101     101 101 101  101

How many terms did Gauss have across his page (it's unlikely that Gauss wrote out all the terms) [21] ? What's the sum of all the terms on the bottom line [22] ? What's the sum of the original arithmetic progression [23] ?

Exercise in Arithmetic Progressions: Calculate the number of casing stones on the Great Pyramid of Khufu at Giza

First a bit of background:

Note
About pyramids in Egypt: Great Pyramid of Giza (http://en.wikipedia.org/wiki/Great_Pyramid_of_Giza), Another look at the Pyramids of Giza by Dennis Balthaser (http://www.truthseekeratroswell.com/ed010108.html) Seven Wonders of the Ancient World (http://en.wikipedia.org/wiki/Wonders_of_the_World#Seven_Wonders_of_the_Ancient_World), Pharaonic monuments in Cairo and Giza (http://www.sis.gov.eg/En/Arts&Culture/Monuments/PharaonicMonuments/070202000000000006.htm). For a likely explanation of in involvement of the number π in the dimensions of the Great Pyramid see Pi and the Great Pyramid (http://www.math.washington.edu/~greenber/PiPyr.html)

The Great Pyramid of Khufu (Cheops in Greek) was built about 2560 BC over a period of 20yrs, employing about 100,000 labourers, moving 800 tonnes of stone a day, excavating, cutting, polishing, transporting and mounting a 15 ton stone every 90 sec. It is the only one of the Seven Wonders of the Ancient World to survive till today. For 3800 yrs it was the tallest man-made structure on earth. The base is horizontal to 15mm, the sides are identical in length to 58mm. The ratio of the circumference of the base (4*440 cubits) to the height (280 cubits) is 2π to an accuracy of 0.04%. The sides of the base are not a square but a 4 pointed star as can be seen by aerial photography at the right time of day (see Aerial photo by Groves 1940 http://www.world-mysteries.com/mpl_2conc1.gif). The closeness of the angles of the base to a right angle (1' deviation) compared to the deviation of the orientation of the pyramid to true north (3' deviation west), allows us to conclude that the pyramid must have been originally oriented to true north and that the continent of Africa has since rotated, with respect to the axis of the earth's rotation, carrying the pyramid with it. The amount of rotation is consistent with estimates from plate tectonics (I read the original paper back in - I think - the '70's but I can't find a reference to it with google).

The pyramid was surfaced by white polished limestone casing stones. Most of the casing stones at Giza were removed to build Cairo. The few that remain today are at the top of the Pyramid of Khafre . The remaining casing stones are dazzling in today's desert sun. Back when the whole pyramid was covered in these stones, it must have been quite a sight as you approached it from over the horizon.

The casing stones on the Pyramid of Khufu are all of the same height and width (to maintain symmetry of the sides), accurate to 0.5mm and weighing 15tons. The casing stones were transported by barge, from the quarry at Aswan, during the Nile flooding, allowing the stones to be deposited next to the pyramid.

Aswan (http://en.wikipedia.org/wiki/Aswan) is famous not only for its quarry which produced syenite (a pink granite used for many of the obelisks in Egypt, and which is also found in our local North Carolina State Park, the Eno River Park), but for being at one end of the arc used in the first estimate of the circumference of the earth by Eratosthenes.

By a fortunate cancellation of two errors of about 20%, Eratosthene's value differed by only 1% from the current accepted value. Christopher Columbus (http://en.wikipedia.org/wiki/Christopher_Columbus) was looking for a route to Japan/India/China across the Atlantic. At the time no ship could carry enough food to last the distance across the Atlantic to China from Europe. As well, a crew would mutiny if required cross open ocean that distance: they might sail off the edge of the earth.

Using the method of Eratosthenes, a century later, Posidonius made his own estimate. Posidonius knew that the star Canopus grazed the horizon at Rhodes (his home) and measured it's maximum elevation as 7.5° (actually 5deg;) at Alexandria. Again by cancellation of errors in the elevation of the star and the distance between the two towns, Posidonius came up with the correct answer.

Note
due to light pollution and atmospheric pollution, on most places on earth nowadays, it's impossible to see any star on the horizon, much less at 5° above the horizon.

Posidonius later corrected the distance from Rhodes to Alexandria, coming up with a now incorrect estimate of the circumference of the earth of 30,000km (18,000 miles). The two estimates of the size of the earth became known in Europe after translation from Arabic (12th Century). Medieval scholars debated the veracity of the two values for centuries, without any attempt to verify the measurement themselves (for in those days truth was revealed, rather than tested, so the matter would be resolved by debate, rather than measurement).

Note
Due to precession of the equinoxes, Canopus no longer reaches an elevation high enough to graze the horizon at Rhodes. Presumably this would have caused some consternation amongst people attempting to reproduce Posidonius's measurement.

Note
Posidonius travelled widely. In Hispania, on the Atlantic coast at Gades (the modern Cadiz), Posidonius studied the tides. He observed that the daily tides were connected with the orbit and the monthly tides with the cycles of the Moon.

To show that his trip was practical, in order to gain financing from Queen Isabella, Columbus used the value 30,000km for the circumference of the earth, and a width of 3,300km (2,000miles) of Sipangu (Japan), making the trip across the Atlantic appear practical. Knowing the potential for mutiny, Columbus kept two logs, one to show the crew, which showed that they'd travelled a shorter distance, as well as the correct log. Columbus was lucky to find the West Indies. His crew, being further from land than anyone had ever been, was within a day of mutiny.

The currently accepted value for the circumference of the earth is 40,000km (thanks to Napoleon).

Note
What does Napoleon have to do with the circumference of the earth [24] ?

Note
You may wonder what Napoleon is doing in a section on the Pyramids of Egypt, but I thought the connection was too strong to pass up the opportunity to talk about it. You should now go onto the internet to find the connection between Napoleon and the Sphinx, which will bring us back to Egypt again.

The dimensions of the casing stones given in Quarries in Ancient Egypt (http://www.cheops-pyramide.ch/khufu-pyramid/stone-quarries.html) gives dimensions for the bottom casing stones of 1 m x 2.5m and 1-1.5m high (6.5 - 10 tons) while the upper casing stones are 1m x 1m and 0.5m high (1.3 tons). Details on the thickness of each layer are at Stone courses of the Pyramid of Khufu (http://www.cheops-pyramide.ch/khufu-pyramid/stonecourses-pyramid.html).

Assuming the same height and width for the casing stones, the number of casing stones in each layer is a (decreasing) arithmetic progression. The height of the pyramid is 146m (482') with a base of 230m (756'). Assuming the pyramid was covered in the upper stones only, how many casing stones would you have needed to order to cover the pyramid? First calculate the number of layers of casing stones (the number of members of the arithmetic series) and the number of casing stones in the first (bottom) and last (top) layers. Then calculate the number of casing stones in a face of the pyramid, then calculate the total number of casing stone for the whole pyramid. Here's my calculations [25] . The actual number of casing stones given in the references is 144,000. Our answer doesn't fit real well.

  • Some bigger stones were used too, this would reduce the estimate further, making our answer worse. It seems reasonable that the size of the casing stones got smaller towards the top and that the bigger stones were at the bottom.
  • The number of layers is 210, not 292.

So what happened to our calculation? The references are unclear as to the size of the facing stones. Further work needs to be done (a class trip to the pyramids?).

Since this wasn't a particularly successful calculation, let's try another. A well known 20th century pyramid is the Transamerica Pyramid (http://en.wikipedia.org/wiki/Transamerica_Pyramid) in San Francisco. The number of windows (from http://ecow.engr.wisc.edu/cgi-bin/get/cee/340/bank/11studentpro/transamericabuilding-2.doc) is 3678. You can get an estimate of the number of floors with windows from transamerica.jpg (http://www.pursuethepassion.com/interviews/wp-content/uploads/2007/08/transamerica.jpg) giving about 45 floors of windows (there are supposed to be 48 floors, presumably the extra 3 floors are in the lower section, which has slightly different architecture). Notice from this photo, that the floors are in pairs, with two consecutive floors having the same number of windows. The lower floor of each pair have longer windows at the ends to make up the extra length. The lowest visible floor has 29 windows on a side. The top floor with windows has 8 windows (transam-coit-01-big.jpg http://www.danheller.com/images/California/SanFrancisco/Buildings/transam-coit-big.jpg). (The different colored top, that starts at the top of the elevator shafts - the "wings" - I think is decorative and not part of the floor count.) Let's see if we've accounted for all the windows. We have windows (starting at the bottom) in this series: 29,29,28,28..8,8. How many members are there in this series [26] ? Let's add the windows in pairs of floors so we now have an arithmetics series: 58,56...16. What's the sum of this series [27] ? There's about 800 windows unaccounted for. (We're not doing real well here either.)

1.6. Logarithms and Slide Rules

I demonstrated slide rules in class. I didn't get to write notes. Slide rules can multiply because the numbers on the C and D scales are layed out so that the log of the numbers are spaced linearly.

For numbers that range over many magnitudes (light intensity, sound intensity) sensors have logrithmic responses. So light which is twice as bright (to a detector counting photons) is not perceived by the eye as being double the intensity. The eye has a range of sensitivity for light of about 107. As well the eyes adjust for ambient conditions, so a white sheet of paper inside a well lit room at night, is reflecting much less light than is the blackest tarred road outside during the day.

The Weber-Fencher Law ("http://en.wikipedia.org/wiki/Weber%E2%80%93Fencher_law") found that the minimal detectable difference in perceived stimuli was a constant fraction of the stimuli, rather than an absolute amount. The Stevens power law (http://en.wikipedia.org/wiki/Stevens%27_power_law) expanded the stimuli to loudness, vibration, brightness, lightness (reflectance), projected length, projected area, redness (saturation), taste, smell, warm/cold, whole body warm/cold, finger span, pressure on palm, muscle force, weight, viscosity, electric shock, vocal effort, angular acceleration, duration.

Logrithmic detectors (e.g. eyes and ears), are often used as part of the introduction to the subject of logrithms. Someone I used to work with, on being told in evening classes about the universality of logrithmic responses in ears, immediately shot up his hand and said "but Sir, elephants have linear ears!". A good teacher will recognize this as an attempt to bring new information to the classes' attention, and an effort to brighten up what is probably a fairly slow going class (no-one enjoys sitting still for 2hrs, particularly after a day's work). A normal reaction by the teacher would attempt to explore this closely related subject for the benefit of the other students in the class.

"Very good Jones. Indeed I didn't know that. Is it true for the Tanzanian elephant too? From what I understand, their hearing has been well studied."

"The Tanzanian elephant? Not sure Sir. I'll have to check. I think I read that bats have linear ears too."

"Good. Very interesting stuff about the bats. Maybe it helps with echo location."

1.7. The Great Numbers of Mathematics

Note
The historical writeup on negative numbers came largely from "e: The Story of a Number, Eli Maor (1994), Princeton University Press, ISBN 0-691-05854-7.

The Great Numbers of Mathematics all appear in Euler's Identity (http://en.wikipedia.org/wiki/Euler's_identity)

eiπ = -1

eiπ + 1 = 0

The Roman number scheme had no zero [28] . The concept of Zero (http://en.wikipedia.org/wiki/Zero) and positional notation entered Europe in the 12th century from the Arab world. It had originated in 628AD in a book by Brahmagupta in India, but was ignored in Europe, mired deep in 1000yrs of ignorance (the Dark Ages). In Central America, the Olmecs were using 0 in the 4th century BC.

Greek mathematics was geometry, for which only positive numbers were needed (lengths, areas and volumes). For the longest time, no-one could handle the concept of less than nothing, much less multiply two negative numbers. Fibionacci (1225) bravely interpreted a negative number in a financial statement as a loss, but this effort was lost on mathematicians. For mathematicians, the idea of subtracting 5 apples from 3 apples was absurd (absurd being one of the names given to negative numbers). Bombelli (b 1530) assigned real numbers to the length of a line, with the 4 operators (+-*/) corresponding to movements along the line and that negative numbers were an extension of the line to the left. It was only when subtraction was recognised as being the inverse of addition, that negative numbers became part of geometry and hence math.

Negative numbers cause no problems for even the youngest people now. It's now obvious that they obey the same rules as positive numbers. However we must remember how much effort and time it took to understand what appears to us now as a trivial step.

The concept of π has been handled well since the beginning of time. However our current understanding of π as a transcendental number is only recent.

i=√-1 is a new concept. Without it, wave functions and much of modern physics and math cannot be understood. see: An Imaginery Tale, The Story of √-1, Paul J. Nahin 1998, Princeton University Press, ISBN 0-691-02795-1.

An example: what pair of numbers have a sum of 2, and a product of 1 [29] ?

What pair of number have a sum of 2, and a product of 2? This problem had no solution till recently. When mathematicians find problems which have a solution for some range of numbers, but not for other quite reasonable looking numbers, they assume that something is missing in mathematics, rather than the problem has no solution for those numbers. The solution to this problem came with the discovery that i=√-1 behaved quite normally under standard operations (+-*/). It took some time for i to be accepted as just another number, since there was initially no obvious way to represent it geometrically. In the initial confusion, numbers using i were called imaginary (or impossible) to contrast them with real numbers. The name imaginary is still in use and is most unfortunate: imaginary numbers are no less real than numbers which represent less than nothing.

The solution: (1+i, 1-i). Add these numbers and multiply them to see the result.

e is the basis for exponential functions. It is also the basis for natural logarithms. Logarithms to base 10 (called common logarithms) are better known. Logarithms are used to substitute addition for the more complicated process of multiplication. Logarithms were a great advance allowing engineering and math multiplications involved in orbital mechanics, surveying and construction. The first log tables were constructed by hand by Napier, who spent 20 years calculating the 107 logarithms to 7 significant figures. Multiplication then involved looking up logs in a book, adding them and then converting back to the number using a table of anti-logarithms. Later the slide rule allowed the same calculations to be done in seconds, provided you could accept the reduced accuracy of 3-4 signicant figures. The slide rule was used to design all the spacecraft in the era of early space exploration (including ones with human crews), as well as bridges, jet engines, aeroplanes, power plants, electronics (in fact, anything needing a multiplication). It wasn't till the mid 1970's that hand held calculators could do a faster job of multiplying than a human using tables of logarithms. Very few engineering companies could afford a computer.

Why did anyone care about orbital mechanics [30] ? It's hardly a topic of conversation in our time.

The great numbers are required for the modern understanding of the physical world. If you're interested in math, a landmark in your progress will be understanding Euler's identity. which you should have by the time you leave high school. If not, you can hold your teachers accountable for failing in their job.

For more info on complex and imaginary numbers, see complex numbers (http://www.austintek.com/complex_numbers/)

1.8. Compound Interest

Bank interest is usually put back into the account, where the interest now draws interest. This process of allowing interest to accrue interest is called compound interest.

Write code to calculate compound interest (call it compound_interest.py). You deposit a fixed amount at the beginning of the year, with interest calculated annually at the end of the year. Variables you'll need (with some initialising values) are

  • principal: $0
  • annual_deposit: $10,000
  • interest_rate: 5%
  • age_start: 18
  • age_retirement: 65

Let's do the calculation for only 1 year. Start with your initial principal, add your deposit, wait a year, calculate your interest and add it to your principal.

Here's my code [31] and here's my output

pip:# ./compound_interest.py
principal   10500.00

Put your interest calculation into a loop (do you need a for loop or a while loop), which prints out your age and principal at the end of each year, using the start and end ages which you've initialised. Here's my code [32] and here's my output

pip:/src/da/python_class/class_code# ./compound_interest.py
age 19 principal   10500.00
age 20 principal   21525.00
age 21 principal   33101.25
age 22 principal   45256.31
age 23 principal   58019.13
age 24 principal   71420.08
age 25 principal   85491.09
age 26 principal  100265.64
age 27 principal  115778.93
age 28 principal  132067.87
age 29 principal  149171.27
age 30 principal  167129.83
age 31 principal  185986.32
age 32 principal  205785.64
age 33 principal  226574.92
age 34 principal  248403.66
age 35 principal  271323.85
age 36 principal  295390.04
age 37 principal  320659.54
age 38 principal  347192.52
age 39 principal  375052.14
age 40 principal  404304.75
age 41 principal  435019.99
age 42 principal  467270.99
age 43 principal  501134.54
age 44 principal  536691.26
age 45 principal  574025.83
age 46 principal  613227.12
age 47 principal  654388.48
age 48 principal  697607.90
age 49 principal  742988.29
age 50 principal  790637.71
age 51 principal  840669.59
age 52 principal  893203.07
age 53 principal  948363.23
age 54 principal 1006281.39
age 55 principal 1067095.46
age 56 principal 1130950.23
age 57 principal 1197997.74
age 58 principal 1268397.63
age 59 principal 1342317.51
age 60 principal 1419933.39
age 61 principal 1501430.06
age 62 principal 1587001.56
age 63 principal 1676851.64
age 64 principal 1771194.22
age 65 principal 1870253.93

You're depositing $10k/yr. How much (approximately) does the bank deposit the first year, the last year [33] ? At retirement age, how much (approximately) did you deposit, and how much did the bank deposit [34] ? The bank deposited 3 times the amount you did, even though it was only adding 5% interest each year. The growth of money in a compound interest account is one of the wonders of exponential growth.

If you're going this route, the payoff doesn't come till the end (and you have to start early). Rerun the calculations for an interest rate of 5%, and starting saving at the age of 20,25,30,35,40.

Table 1. Final principal at retirement as a function of starting age

start age, yrs principal at retirement, $
18 1,840,000.00
20 1,676,851.64
25 1,268,397.63
30 948,363.23
35 697,607.90
40 501,134.54

How long do you have to delay saving for your final principal to be reduced by half (approximately) [35] . There is a disadvantage in delaying earning by doing lengthy studies (e.g. a Ph.D) (you may get a lifestyle you like, but at a lower income on retirement). Rerun the calculations (at 5% interest) for a person who starts earning early (e.g. a plumber) and a Ph.D. who starts earning at 30, but because of a higher salary, can save $20k/yr.

Table 2. Principal at retirement: Plumber, Ph.D.

Plumber Ph.D.
1,840,000.00 1,896,726.45

Another property of exponential growth is that small increases in the amount of interest, have large effects on the final amount. Rerun the calculations for an interest rate of 3,4,5,6,7%. Here's my results.

Table 3. Final principal at retirement at function of interest rate

interest rate, % principal, $
3 1,034,083.96
4 1,382,632.06
5 1,840,000.00
6 2,555,645.29
7 3,522,700.93

If your interest rate doubles (say from 3% to 6%), does your final principal less than double, double, or more than double? People spend a lot of time looking for places to invest their money at higher rates. Investments which offer higher interest rates are riskier (a certain percentage of them will fail) and the higher interest rate is to allow for the fact that some of them fail (you are more likely to loose your shirt with an investment that offers a higher rate of return).

For your upcoming presentation, prepare a table showing your balance at retirement as a function of the age you started saving; show the amount of money you deposited and the bank deposited. Show the balance as a function of the interest rate. Be prepared to discuss the financial consequences of choosing a career as a plumber or a Ph.D.

1.9. Mortgage Calculation

While the above section shows that at Ph.D. and a plumber will be about even savings-wise at retirement, there is another exponential process going against the person who does long studies: the interest you but a house on a mortgage (rather than cash, which most people don't have). (The calculations below apply to other loans, e.g. student loans.)

First, how a mortgage works: In the US, people, who want to buy a house and who don't have the cash to pay for it (most of them), take out a mortgage (http://en.wikipedia.org/wiki/Mortgage). A mortgage is a relatively new invention (early 20th century) and was popularized by Roosevelt's New Deal (see Roosevelt's New Deal http://www.bestreversemortgage.com/reverse-mortgage/from-roosevelts-new-deal-to-the-deal-of-a-lifetime/)

Previously only rich people could buy a house; the rest rented. In places like US, renters were sentenced to a life of penury and beholden to odious landlords. In other places (e.g. Europe), owning a house was never seen as being particularly desirable, most people choose to rent and presumably the relationship of a renter with the owner (more often a Bank than a person) was more amicable than in the US. In Australia, people just expect that everyone should own their house (and they do), because that's just the way that it's supposed to be, so most people take out a mortgage as soon as they start earning. In (at least Sydney where I grew up) Australia, changing jobs doesn't require you to move. The city has a great train system, and if you change jobs, you just get off the train at a different stop. A person paying off a mortgage in Sydney won't have to sell their house when they get a new job. In the US, a change of job usually requires you to sell your house, and move. Most US towns are small, and there is only ever 1 job for any person. If you loose your job in the US, you have to move. This arrangement keeps people in jobs they hate. The US job system wrecks the main feature of a mortgage, which is that you don't get any benefit of buying a house on a mortgage, unless you own it for a while (this will be explained below).

The essential features of a mortgage are

  • The mortgagee (the person living in the house), has a loan from the mortgagor (the person who comes up with the money)
  • The mortgagee agrees to pay back the loan at a certain rate (faster payoff is also acceptable).
  • The mortgagor holds a lien on the house in case of default on the loan by the mortgagee
  • The agreement expires (is dead - mortgage=="dead pledge") on full payment of the loan. In the US the loan is usually paid off in 30yrs (or less often, in 15yrs).
  • In Switzerland and Australia, the mortgage period is 3-5yrs. In these systems, you only pay off a small amount of the mortgage, and you have to get a new mortgage at the end of the mortgage period. Why the system is different to the US system will be explained below.

Let's see how the US system works: The median house price in the US in 2008 is 250k$. A mortgage lending company expects a deposit of at least 5% of the house price (to show that you're capable of at least a token level of saving, the actual amount is 5-20% depending on the politics and the economics of the moment, but we'll use 5% for the calculations here), does a credit check on you (makes sure you've paid off your other loans, and that you have a steady job), checks that the house is in good condition and is worth the amount of the mortgage, and then after the mortgagee pays legal fees etc, the mortgage is issued and the mortagee gets a book of coupons to fill in, one each month and an address to send the monthly check (cheque) (usually the mortgagee arranges for their bank to do it automatically).

Let's say you've just scraped the 5% deposit and fees and have got a mortgage for 95% of 250k$ (23k75$) at 6% interest/year. If you don't pay off any principal (i.e. the loan), what will be your monthly interest [36] ? You don't want a mortgage like this (called a Balloon Mortgage) as you're not paying off any of the principal. You'll be paying interest forever. Let's say instead that you decide each month, to pay off $100 of principal along with the interest payment. What will be your first month's payment [37] ? If for the 2nd month you pay off another $100 of principal, and continue to pay interest on the debt at 6%/yr, will your second month's payment be less than, the same as, or more than the first month's payment [38] ?

A mortgage like this will have a slowly reducing payment each month, since you will be paying interest on a reduced amount of principal. Having a different payment each month is difficult for the mortgagee to track. The accepted solution is to keep the monthly payment constant and to let the accountants (or now computers) handle the varying amounts of principal and interest each month. Using this algorithm, the mortgagee pays the same amount ($1287) each month, but in the 2nd month has an interest payment lower by $6.44. The mortgagee instead puts the $6.44 towards payment of principal. Here's the calculations for the 2nd month with a constant monthly payment.

1st month:
principal at start          =237500.00
interest on principal at 6% =           1187.00
principal                   =            100.00
1st payment (end month)     =           1287.50

2nd month:
principal at start          =236212.50 
interest on principal at 6% =           1181.06
payment of $106.44 principal=            106.44
2nd payment (end month)     =           1287.50

This schedule (constant payments) has lots of advantages for both the mortgagor and mortgagee based on the following assumptions

  • the mortgagee starts off paying as much as they can afford
  • The mortgagee's (real) salary will increase with time, meaning that after an initial period of eating peanut butter for dinner, that the mortgagee's financial pressure of paying off the mortgage will decrease. Because of this, the mortgagee will be unlikely to default in the far future (the likelihood of default in the near future having been taken care of by the pre-mortgage vetting).
  • The value of the house will increase with time. It's unlikely that the mortgagee will still be in the house when the mortgage is paid off (they will move before then), and the mortgagor will be paid back out of the increase in value of the house.

    Let's say that the house price in 2008 = $250k. In 2018, the house is worth $350k, while the mortgagee has paid off $110k in principal. Here's the mortgagee's accounts

    	           mortgage liability                  house asset               cash (check/cheque account)
    	              D        C                       D        C                  D       C
    	           ------------------               ---------------             ---------------
    2008 
       buy house               |  250k                    250k  |                          |
    
    2008-2018
       payments          110k  |                                |                          | 110k
       incr. value             |                          100k  |  
    
    2018 
       sell house              |                                | 350k                350k |      
       pay-off mortage   140k  |                                |                          | 140k   
    	             -------------                    -------------               -----------
    balance               0k   |                            0k  |                     100k |
    

    In 10yrs the morgagee has paid off the mortgage and walked away with 100k$ in cash (the increase in the value of the house). However the mortgagor has got their money back in 10yrs (and can loan it out again) as well as earning 110k$ in interest. The financial people will slap the mortgagee on the back and congratulate him/her for having the business acumen to wind up with 100k$ in his/her hands, just by sitting there while the value of the house appreciated. While the mortgagor will have got a great deal out of this, the mortgagee, due to the increase in costs of houses, will likely have to pay $500k for their next house.

    While this isn't a great bargain for the mortgagee, it's often better than the next best choice, which is to pay rent forever, which pays off the mortgage for the owner of the rental unit.

The things that (can) go wrong with a mortgage are

  • People's real salaries don't increase (at least they haven't in the US). Despite the technological advances, workers real salaries (the amount of time they need to work to buy say a loaf of bread, or a car) hasn't changed since the 50's or 60's. One would think with the mechanisation of just about every industry, that the farmer would need less time to produce the wheat for a loaf of bread (or can produce more wheat for the same time), and that cars, refrigerators etc can be produced with less time. As well people are better educated now and can be expected to be more productive.

    Possible reasons for this stagnation in real salaries, and in disposable income (what you have left to spend after paying for essentials, like food, rent, medical care)

    • The increased productivity is not being returned to the workers. The money could instead be sent to high paid executives (the ratio of salary of the top 10% of workers to the bottom 10% is steadily increasing);
    • To pay for expensive factory equipment, whose short life nullifies the increase in productivity.
    • People spend money on gadgets that didn't exist 50yrs ago (ipods, computers, wide screen TVs)

(Some) people don't use their money wisely. Rich people spend their money differently to poor people: a poor person will buy an expensive SUV or PickUp truck, whose value in 5-10yrs will be $0. A rich person, who has a lot more money, will buy an adequate car, knowing that it's value will quickly go to $0, and put the rest of their disposable income into some investment; a bigger house, education, lending money to other people for mortgages, stocks, art.

Back to the mortgage calculation: There is a formula for doing these calculations (see Mortgage Calculator http://en.wikipedia.org/wiki/Mortgage_Calculator). Rather than teach you the math neccessary to understand the derivation of the formula, we'll program the computer to do the calculations by brute force.

Note
Mortgage calculator webpages are everywhere on the internet.

Write code mortgage_calculation.py to include the following

  • The name of the program, the author, date, a brief description of the functions
  • initialise the following variables (you can swipe this with your mouse):
    Note
    there's no magic to where these variables come from. You start with the two obvious variables, initial_principal and interest, and start writing code. As you need more variables, you add them to the initialisation section of the code (called bottom up programming) (see Top Down, Bottom Up). You could of course sit down and think about the code and figure out all the variables you need from scratch (top down programming). For a piece of code this size (i.e. small) you would do it by bottom up programming. Sometimes you'll have to rename variables you've already declared (there'll be a namespace collision; you'll want to use the same name in two places). You have to sort this out on the fly by using an editor with a simple find/replace capability.
    initial_principal = 250000.0    #initial value, will decrease with time
    principal_remaining = initial_principal
    principal_payment = 0.0
    
    interest  = 0.06                #annual rate
    interest_payment = 0.0          #monthly interest
    total_interest_paid = 0.0       #just to keep track (in the US, if you itemise deductions,
    	                        #the interest on a mortgage for your primary residence is tax deductable),
    
    initial_principal_payment = 100.00        #This is the initial value of the monthly principal payment
    time_period = 0
    
  • Next calculate the monthly_payment. This is fixed for the life of the mortgage. How do you calculate this [39] ? Check that your code runs.
  • print a column header using this code
    #print column headers
    print " time    princ. remaining     monthly_payment   principal payment    interest payment      total int paid"
    
    
  • Use this piece of code whenever you need to print out the monthly balances.
    #print balances at the beginning of the mortgage
    print "%5d %19.2f %19.2f %19.2f %19.2f %19.2f" %(time_period, principal, monthly_payment, principal_payment, interest_payment, total_interest_paid)
    
  • write code to show the balances at the beginning of the mortgage. Run your code.
  • write code to show the balances at the end of the first month. How do you calculate principal_payment for each month (hint [40] )? Run your code.
  • Also print out the total price paid. This is (total_interest_paid + initial_principal - principal_remaining) but there's a simpler way (hint [41] ).

Here's my code [42] and here's my output

dennis: class_code# ./mortgage_calculation.py 
 time    princ. remaining     monthly_payment   principal payment    interest payment      total int paid
    0           250000.00             1350.00                0.00                0.00                0.00
    1           249900.00             1350.00              100.00             1250.00             1250.00

Copy mortgage_calcultion.py to mortgage_calculation_2.py. Add/do the following checking that the program produced sensible output at each stage.

  • Put the code that calculated the balances at the end of the first month into a loop that continues till there is no principal left to pay (is this a for or while loop?).
  • Initially print out the results of each iteration, then change your code to only output at the end of each year.
  • Change initial_principal_payment so that the mortgage is paid out in 30 yrs (doesn't have to be exact, you can stop iterating when you have a balance of less than half a payment).
  • Add an extra column which prints out the total amount of money the mortgagee pays out.
  • At the top of the output (before the monthy stats), output the initial principal payment and the annual interest (as a number e.g. 0.06, or as a percent e.g.6%).
  • At the end, print out a line like this
    interest 0.0700, monthly_payment 2248.33, initial_principal_payment 790.00, ratio (total cost)/(house price) 1.6188
    

Here's my code [43] and here's my output

dennis: class_code# ./mortgage_calculation_2.py
 initial_principal_payment              248.88 annual_interest 0.0600
 time    princ. remaining     monthly_payment   principal payment    interest payment      total int paid       total payment
    0           250000.00             1498.88                0.00                0.00                0.00                0.00
   12           246929.97             1498.88              262.91             1235.96            14916.49            17986.51
   24           243670.60             1498.88              279.13             1219.75            29643.62            35973.02
   36           240210.19             1498.88              296.34             1202.53            44169.72            53959.54
   48           236536.35             1498.88              314.62             1184.25            58482.40            71946.05
   60           232635.91             1498.88              334.03             1164.85            72568.47            89932.56
   72           228494.91             1498.88              354.63             1144.25            86413.98           107919.07
   84           224098.50             1498.88              376.50             1122.37           100004.08           125905.58
   96           219430.92             1498.88              399.72             1099.15           113323.02           143892.10
  108           214475.46             1498.88              424.38             1074.50           126354.07           161878.61
  120           209214.36             1498.88              450.55             1048.32           139079.48           179865.12
  132           203628.77             1498.88              478.34             1020.54           151480.40           197851.63
  144           197698.67             1498.88              507.84              991.03           163536.81           215838.14
  156           191402.81             1498.88              539.17              959.71           175227.47           233824.66
  168           184718.64             1498.88              572.42              926.46           186529.81           251811.17
  180           177622.20             1498.88              607.73              891.15           197419.88           269797.68
  192           170088.07             1498.88              645.21              853.67           207872.26           287784.19
  204           162089.25             1498.88              685.00              813.87           217859.96           305770.70
  216           153597.09             1498.88              727.25              771.62           227354.30           323757.22
  228           144581.14             1498.88              772.11              726.77           236324.87           341743.73
  240           135009.11             1498.88              819.73              679.14           244739.35           359730.24
  252           124846.70             1498.88              870.29              628.58           252563.45           377716.75
  264           114057.49             1498.88              923.97              574.91           259760.76           395703.26
  276           102602.83             1498.88              980.96              517.92           266292.61           413689.78
  288            90441.67             1498.88             1041.46              457.42           272117.96           431676.29
  300            77530.43             1498.88             1105.70              393.18           277193.23           449662.80
  312            63822.86             1498.88             1173.89              324.98           281472.18           467649.31
  324            49269.84             1498.88             1246.30              252.58           284905.66           485635.82
  336            33819.22             1498.88             1323.16              175.71           287441.55           503622.34
  348            17415.63             1498.88             1404.77               94.10           289024.48           521608.85
  360                0.31             1498.88             1491.42                7.46           289595.67           539595.36
 ratio (total cost)/(house price) 2.1644

A few things to notice

  • How much does your 250k$ house cost you at 6% interest [44] ?
  • How long does it take for you to half own your house at 6% interest [45] ?
  • The average US house owner is in their house 5-7yrs, moving 11 times in their life (see The Evolution of Home Ownership http://www.homeinsight.com/details.asp?url_id=7&WT.cg_n=Publications&WT.cg_s=0&GCID=bhph1). How much of your house do you own after 5yrs, how much interest have you paid [46] ? America (well business interests) take great pride in the mobility of its work force. People are prepared to move towns for better jobs (or be faced with having no job at all). If you move every 5-7yrs, all you're doing is paying interest to a mortgage company and not building up equity. You should move to a place where, if you have to change jobs every 5yrs, you won't have to move. This means a big city with a good public transport system and an economy that has stable jobs.

Run the code, changing the interest rates (5,6,7,8%) and the monthly payment, to get a mortgage of 30,15,10 and 5yrs. Find the cost/month and total price paid/price of house at purchase. Here's my results.

Table 4.

Mortgage Calculations, 250k$ house: interest rate, loan period

monthly payment, initial principal payment, (total price)/(value of house)

interest rate,% 30yr 15yr 10yr 5yr
3 $1050, $425, 1.53 $1725, $1100, 1.25 $2405, $1780, 1.16 $4475, $3850, 1.09
4 $1193, $360, 1.72 $1843, $1010, 1.33 $2533, $1700, 1.22 $4633, $3800, 1.11
5 $1341, $300, 1.93 $1976, $935, 1.43 $2651, $1610, 1.27 $4716, $3675, 1.15
6 $1500, $250, 2.16 $2110, $860, 1.52 $2776, $1526, 1.33 $4834, $3584, 1.16
7 $1663, $205, 2.39 $2248, $790, 1.62 $2903, $1445, 1.39 $4950, $3492, 1.19
8 $1834, $168, 2.64 $2390, $723, 1.72 $3033, $1367, 1.45 $5066, $3400, 1.24
Note

For some perspective:

  • In the US, a good rate for 30yr mortgage, in the 1990-2000's has been 6%.
  • In the US, the mortgage rates during the stagflation of the 1980s was upto 14%.
  • Short term mortgages (3-5yrs) are common in Switzerland and Australia, but are not the norm for standard house buyers in the US. At the end of a short term mortgage, you expect to take out another mortgage, at a different rate, till you pay off your house.
  • In the US, car loans are for about 5yrs. In this case, divide all the above numbers by a factor of 10 (or so). The problem with taking out a car loan, it that the value of the car drops by 25% just driving it out of the car lot. You'll owe money on selling the car, right till the end of the car loan.

A few things to note (in the US a typical house mortgage is 30yrs, a typical car loan is 5yrs)

  • For long loans (30yrs), most of your payments are interest (at 8%, you pay in interest an extra 165% of the price of the loan)
  • For short loans (5yrs), most of your payments are principal (at 8%, you pay in interest an extra 25% of the price of the loan)
  • Because most of the payments for a long loan are interest, the total payment is strongly affected by the interest rate. For a loan of 30yrs, increasing the interest from 5 to 8% (an increase in interest rate of 60%) increases the total payment by 70%.
  • For a short loan (5yrs), because most of your payments are principal, increasing the interest rate by 60%, from 5 to 8%, only increases the total payment by 9%.
  • You don't have a lot of choice about the interest rate. The interest rate varies as a function of the health of the economy. Your only choice (if you think the interest rate is too high) is to keep renting.
  • I've been the mortgagee on several loans. It was obvious that as the interest rate goes up, the monthly payment goes up, but I didn't realise that the initial principal payment goes down. Here's the data for two 30yr loans, the first at 5%, the second at 8%
    	                 5%                                    8%
     time    monthly_payment   principal payment   monthly_payment   principal payment 
        0            1341.67                0.00           1833.67                0.00       
       12            1341.67              314.04           1833.67              179.66 
       24            1341.67              330.11           1833.67              194.58
       36            1341.67              347.00           1833.67              210.72
       48            1341.67              364.75           1833.67              228.21
       60            1341.67              383.41           1833.67              247.16
       72            1341.67              403.03           1833.67              267.67
       84            1341.67              423.65           1833.67              289.89
       96            1341.67              445.32           1833.67              313.95
      108            1341.67              468.10           1833.67              340.00
      120            1341.67              492.05           1833.67              368.23
      132            1341.67              517.23           1833.67              398.79
      144            1341.67              543.69           1833.67              431.89
      156            1341.67              571.51           1833.67              467.73
      168            1341.67              600.74           1833.67              506.55
      180            1341.67              631.48           1833.67              548.60
      192            1341.67              663.79           1833.67              594.13
      204            1341.67              697.75           1833.67              643.44
      216            1341.67              733.45           1833.67              696.85
      228            1341.67              770.97           1833.67              754.69
      240            1341.67              810.42           1833.67              817.33
      252            1341.67              851.88           1833.67              885.16
      264            1341.67              895.46           1833.67              958.63
      276            1341.67              941.27           1833.67             1038.20
      288            1341.67              989.43           1833.67             1124.37
      300            1341.67             1040.05           1833.67             1217.69
      312            1341.67             1093.26           1833.67             1318.76
      324            1341.67             1149.20           1833.67             1428.22
      336            1341.67             1207.99           1833.67             1546.76
      348            1341.67             1269.80           1833.67             1675.14
      360            1341.67             1334.76           1833.67             1814.17
    
    While at the higher interest rate, the initial payment of principal is about 50% lower, at the end of the loan, you're paying about 50% more in principal each month. The 5% loan is half paid out at 20yrs, while the 8% loan is half paid out in 22yrs.
  • If you're in your house for 5-7yrs (let's say 6yrs, and let's say you're paying at a historically low interest rate of 6%), how much equity do you have in the house (i.e. how much of the house have you paid off, how much do you own) and how much interest have you paid [47] ? When you sell the house, you only keep 20% (22/(22+86)) of the money you paid out. The rest goes to the mortgage company in interest. If you stay in your house for only 6yrs, you're not much better off than if you were renting. (You can assume that the rental price is about 80% of the monthly mortgage rate. The quantity measured is called the "price to rent ratio" and is the number of months of rent that you would have to pay to own the house.)

    Moving is an expensive proposition: you have to pay real estate agent fees, down time while you move, the expense of moving your possessions and then there's the personal disruption of finding a new doctor, dentist, car mechanic, school(s), friends, people who have similar interests/hobbies. It takes about 2yrs to recover from a move. People living in a society where they (have to) move every 5-7 yrs are working to enrich the banks, moving companies and real estate agents. And what for? Was there a real good reason to move for a job? Why not have jobs in the place you're living? The rest of the world (outside the US) doesn't find this difficult to arrange. In those countries the citizens pay (via taxes) for train systems, so that they don't have to move if the new job is across town. This is a lot cheaper and less disruptive than moving. Why should you have to go to a new school because one of your parents got a new job?

    You should live in a society, where workers benefit from their earnings, rather than having to pass it on to other people. Requiring people to move is good for the economy (lots of money is spent), but not good for the person: there is no wealth creation.

How does the interest paid on a mortgage make life easier for the plumber than for the Ph.D? The Ph.D. starts earning later (30yrs old) and is doing well to have paid off their house by the time they retire. Usually the Ph.D. has to move several times, and spends a lot of time paying off interest early in each mortgage. The plumber (hopefully) has a stable business, doesn't move and starts earning when they're 20. The plumber will own his house well before he retires.

Why is it possible to get a 30yr fixed rate mortgage in USA, whereas in other countries, 3-5yrs is the norm? How can banks in USA forecast the country's economy for 30 years into the future and know that they'll make a profit from the loan, whereas no bank in the rest of the world is prepared to make a loan for more that 3-5yrs? The answer is that the banks in USA are no more capable of predicting the economic future than anyone else. In USA, the banks know that the average person stays in their house for 5-7yrs and then has to move. Almost no-one in USA stays in their house for 30yrs. The 30yr fixed rate mortgage is really a 5-7yr mortgage. In the rest of the world, people don't have to move when their job changes, and the banks can't write mortgages for longer than they can forecast the state of the economy.

Similarly, in the US, credit card companies don't have annual fees on their standard credit cards. Why not? Enough people don't pay off their credit cards at the end of the month, incurring interest at rates of upto 37%. The interest on unpaid balances is enough to keep the credit card companies in good financial shape. In the rest of the world, people pay off their balance every month as so credit card companies charge annual fees for the credit card.

With people not trusting other forms of investment (e.g. the stock market), the pressure to buy a house forces up the prices of houses. If for any reason (lack of credit, downturn in the economy) people stop buying houses, then the price of houses crashes.

1.10. Refinancing a Mortgage

With a 30 year fixed rate mortgage being so long, you might find during the time of the mortgage, that the interest rate for the new loans has dropped and is less than the rate you're paying for yours. In this case, you can do what's called a refinance. You get a new mortgage for the remaining amount of principal. This mortgage pays off your old mortgage, so the previous lender gets their money, and you get to pay out on the new mortgage at a lower rate.

Sounds great huh? The wrinkle is fees. You have to put up some money to get a mortgage and then to get a new mortgage. Someone (your mortgage broker) has to check that your house is in good condition, that it's worth at least the principal left on the current mortgage, that you are still in good financial standing, go register deeds and do a whole lot of paper work. (You have to sign a whole lot of paper work too. It takes a couple of hours in a lawyer's office, but they do all the work, you just sign and pay the money.) So while you may be getting a better deal with your refinancing of the mortgage, it will cost you money. Let's find out if it's worth refinancing.

Let's say you've been in your house for 5 yrs (i.e. you have 25 yrs left on your current mortgage at 5.25%) and the interest rate drops to 4.75%. The balance left on your mortgage is 160k$ and fees on a refinance are $3,200. Should you refinance?

Some quick numbers: with a decrease in interest of 0.5%, how much will you save in your first year [48] ? You save about $70/month. How long will it take for your new loan to pay off the fees [49] ? You've given your mortgage broker $3200 and you're going to get it back through a lower interest rate at $800/yr. If you aren't going to be in your house for at least 4yrs, you've lost money.

Let's say you're going to be in your house for at least 4yrs. Lets do the numbers more closely. The mortgage broker will be giving you a new 30yr mortgage at 4.75%. Let's compare the total amount of money you'd pay in the remaining time (25yrs) on your current 160k$ mortage at 5.25% and the total amount you'd pay on the new lower interest (4.75%) mortgage for 30yrs. The usual thing to do with the fees is to add them to the loan. This assumes you'd rather keep your $3,200 and invest it in something that gets a better return than 4.75% and instead you borrow $3,200 from the mortgagee and pay it back at 4.75%. So your 4.75% loan is for 163k$. Run your mortgage calculator (get the principal right to the nearest $). Here's my results

Table 5. Comparison of 4.75% 30yr and 5.25% 25 yr mortgage on 160k$

Mortgage amount interest time payments initial princ. monthly total
160k$ 5.25% 25yr $259 $959 288k$
163k$ 4.75% 30yr $205 $850 306k$
savings     $110 -18k$

You're saving you $110/mo. This is not too bad, but look at the last column, the one that you're interested in if you're going to stay a long time (which you should if you buy a house). The house is going to cost you 18k$ more (some of that is the extra 3k$ in fees, plus interest). You've got a short term benefit in exchange for a long term loss. Why? You've forgotten that the longer the mortgage the more you pay in interest. You're comparing a 25yr loan with a 30yr loan. The 30yr loan will cost you more, almost no matter what the interest rate. You would have noticed a drop in monthly payments if you'd changed from a 25yr mortgage to a 30yr mortgage at the same interest rate. You have to compare mortgages of the same period. The only way you're going to get ahead with refinancing is if you keep the same period or pay at the same rate as before. The mortgage broker can only offer you a 30yr loan (not a 25yr loan), but you're allowed to pay off extra money any time you want. You can set your monthly payments to have the mortgage finish in 25 yrs if you like.

Instead look at the results if you pay the new mortgage for 25yrs or you start paying at $259/mo. Note the results are different if you're looking for short term benefit (monthly payment) or long term benefit (total cost of the house).

Table 6. Comparison of 5.25% 25 yr mortgage for 160k$ with refinanced mortgage at 4.75%

Mortgage amount interest time payments initial princ. monthly total
160k$ 5.25% 25yr $259 $959 288k$
163k$ 4.75% 26yr $259 $904 282k$
163k$ 4.75% 25yr $284 $929 278k$
savings (best long term)     $30 10k$

You're paying 10k$ less for the house, which is good, but notice the short term gain isn't much. You're saving $30/mo: it will take how long to pay off the refinancing fees [50] ? If you're looking for a long term advantage when refinancing, don't expect it to help in the short term.

A millstone around your neck here is the fees you paid the mortgage broker. You can pay extra into the mortgage any time you like. How about instead of refinancing, you just put the $3200 towards your principal? Look at your 25yr 5.25% mortgage reduced by 3k$

Table 7. Comparison of 5.25% 25 yr mortgage for 160k$ with same mortgage reduced by 3k$

Mortgage amount interest time payments initial princ. monthly total
160k$ 5.25% 25yr $259 $959 288k$
157k$ 5.25% 24+yr $259 $945 272+8=280k$
savings     $14 8k$

After depositing 3k$, your mortgage ends in middle of the last year. At the end of the 24th year, you've paid out 272k$ and have another 8k$ to pay. Paying off 3k$ now, saves you 8k$ over the life of the mortgage. How did 3k$ become 8k$? Calculate the result of 25yrs of 5.25% interest on 3k$ [51] . I don't know why one answer is 11k$ and the other answer is 8k$. Rerun the calculation to find the interest rate that gives 8k$ principal after 25yrs [52] . A balance of 8k$ corresponds to an interest rate of 4%. At 0.5% drop in interest rates, there's only a 2k$ difference between refinancing and putting the refinancing fees into your current mortgage.

Here's the summary

  • refinance 0.5% lower for 30yrs
    • short term benefit: $110/mo, recoup fees in 4yrs
    • long term loss: 18k$ more in total payment
  • refinance 0.5% lower, pay at 25yr rate
    • short term benefit: $30/mo, recoup fees in 9yrs
    • long term benefit: 10k$ lower total payment
  • do not refinance, put 3k$ fees into principal in current mortgage
    • short term benefit: none
    • long term benefit: 8k$ lower total payment

The rule of thumb is that you don't benefit from a refinance unless the interest rate drops 1%. Whether this rule applies to you, depends on whether you expect to be out of the house in the short term or still in it when the mortgage is paid out.

1.11. Moore's Law: effect on calculation of e=(1+1/n)^n

Note
I wish to thank my co-worker, Ed Anderson, for the ideas which lead to this (and the next few) sections.

In 1965 Gordon Moore observed that starting from the introduction of the integrated circuit in 1958, that the number of transistors that could be placed inexpensively on an integrated circuit had doubled about every 2yrs. This doubling has continued for 50yrs and is now called Moore's Law (http://en.wikipedia.org/wiki/Moore%E2%80%99s_law). Almost every measure of the capabilities of digital electronics is linked to Moore's Law: processing speed, memory capacity, number of pixels in a digital camera, all of which are improving at exponential rates. This has dramatically improved the usefullness of digital electronics. A (admittedly self serving) press release says that the new Cray at the CSC Finnish Center for Science (http://www.marketwire.com/press-release/Cray-Inc-NASDAQ-CRAY-914614.html) will take 1msec to do a calculation that the center's first computer, ESKO, installed half a century earlier, would have taken 50yrs to complete.

The conventional wisdom is that Moore's Law says that the speed of computers doubles every 2yrs, but Moore didn't say that; Moore's Law is about the number of transistors, not speed. Still the speed of computers does increase at the Moore's Law rate. What Moore is describing is the result of being able to make smaller transistors. A wafer of silicon has certain irreducible number of crystal defects (e.g. dislocations). Multiple copies (dozens, hundreds, thousands, depending on their complexity) of integrated circuits (ICs) are made next to each other in a grid on a silicon wafer. Any IC that sits over a crystal defect will not work and this will reduce the yield (of ICs) from the wafer driving up costs for the manufacturer. Since on average the number of defects in a wafer is constant, if you can make the ICs smaller, thus fitting more ICs onto the wafer, then the yield increases.

Transistors and ICs are built onto on a flat piece of silicon. What is the scaling of the number of transistors (or ICs) if you reduce the linear dimensions of the transistors by n [53] ? The thickness (from top to bottom looking down onto the piece of silicon) decreases by the factor of n too, but since you can't built transistors on top of each other (well easily anyway) you don't get any more transistors in this dimension.

One consequence of making ICs smaller, is that the distance that electrons travel to the next transistor is smaller. With the speed of light (or the speed of electrons in silicon) fixed, the speed of the transistors increased. As well the capacitance of gates was smaller, so that less charge (less electrons) was needed to turn them off or on, increasing the speed of the transition. Silicon wasn't getting any faster, just the transistors were getting smaller. Manufacturers were driven to make smaller transistors by the defects in silicon. The increase in speed was a welcome side effect.

Let's return to the numerical integration method for calculating π to 100 places, which for a 1GHz computer would take about 10100-9=91secs or 1074*ages of the Universe, and see if Moore's Law can help. Let's assume that Moore's Law continues to hold for the indefinite future (we can expect some difficulties with this assumption; it's hard to imagine making a transistor smaller than an atom) and that for convenience the doubling time is 1yr. Now at the end of each year (31*106=107.5secs), instead of allowing the calculation to continue, you checkpoint your calculation (stop the calculation, store the partial results) and take advantage of Moore's Law, by transferring your calculation to a new computer of double the speed. How long does your calculation take now?

Write code (moores_law_pi.py) that does the following

  • Has the name of the file, the author, and a brief description of the purpose
  • Initiallises the following variables
    • year = 10**7.5 #secs
    • doubling_time = year
    • time = 0
    • iterations_required = 10**100 #iterations
    • iterations_done = 0 #iterations
    • initial_computer_speed = 10**9 #iterations/sec (it's actually clock speed. iterations/sec will be down by 10-100 fold, assuming 10-100 clock cycles/iteration. This is close enough for the answer here.)
    • computer_speed = 0 #iterations/sec
  • have the code calculate the number of iterations done in the first year and print out the time (in years) and the number of iterations done. Use %e to print out the number of iterations in scientific format.

Here's my code [54] and here's the output.

pip:# ./moores_law_pi.py
elapsed time (yr)     1, iterations done 3.162278e+16

You now want your code to loop, with the speed of the machine doubling each year.

  • will this be a while or a for loop?
  • Will you have a loop variable? How will you update time?
  • how will you test that the loop should exit?
  • what code will be in the loop? (You have to update something, calculate something, store and print out the result.)
  • how will you change the speed of the computer each year?

Here's my code [55] and here's the last line of my output.

elapsed time (yr)   278, iterations done 1.535815e+100

This is a great improvement; instead of 1074 ages of the Universe, this only takes 278yrs. You can easily finish the calculation in the age of a single Universe with time to spare.

50% more calculations were done than asked for, because we only check at the end of each year and we reached our target part way through the year. Assuming that the calculation (i.e. 10100 iterations), finished exactly at the end of the year, when was the job half done [56] ? If you'd started the job at the end of the 278th year, how long would the job have taken [57] ?

1.12. Optimal Slacking and the Futility of Interstellar Space Travel

If you have a process whose output increases exponentially with some external variable (e.g. time), then you don't start till the latest possible moment. In The Effects of Moore's Law and Slacking on Large Computations (http://arxiv.org/pdf/astro-ph/9912202) Gottbrath, Bailin, Meakin, Thompson and Charfman (1999) shows that if you have a long calculation (say 3 yrs) and in 1.5yrs time you will have computers of twice the speed, then it would be better from a productivity point of view to spend the first 1.5yrs of your grant surfing in Hawaii, then buy the computers and run the calculation at twice the speed in the 2nd 1.5yrs.

Boss: "Gottbrath! Where the hell are you and what are you doing?"

Gottbrath: "I'm working sir!"

If you purchase a computer with speed as a requirement, you don't purchase it ahead of time (like 6 months or a year ahead) as it will have lost an appreciable fraction of its Moore's Law speed advantage by the time you put it on-line. With government computer procurements taking a year or so, there is no point in comparing computers with a 25% speed difference; they will lose half their speed while waiting for the procurement process to go through.

Interstellar space travel will take centuries, while back on earth there will be exponential improvements in technology. The result of this will be that the first party to leave on an interstellar trip will be overtaken in 100yrs time by the next party. Figure 1 in the Gottbrath paper shows the effect of delaying starting a job, when the computers double in speed every 18months (Moore's Law time constant). The jobs starting later all overtake the early jobs. This graph could be relabled "the distance travelled through space by parties leaving earth every 5 months", using the exponentially improving (doubling every 18mo) space travel technology. The curves all cross eventually. The first party to arrive at the interstellar destination will be the last to leave.

1.13. Geometric Progressions: Summing the series

Since this is a computer class, I had you write a computer program to calculate the time taken for the Moore's Law improvement on the π calculation. However you could have done it your head (a bit of practice might be needed at first). The speed of the Moore's Law computer as function of time is a Geometric Progression (http://en.wikipedia.org/wiki/Geometric_sequence). Example geometric progressions:

1,2,4,8,16,64

1,0.5,0.25,0.125...

A geometric series is characterised by the scale factor (the initial value, here 1) and the common ratio (the multiplier) which for the first series is 2, and for the 2nd series is 0.5. Geometric progressions can be finite, i.e. have a finite number of terms (the first series) or infinite, i.e. have an infinite number of terms (the second series). When the common ratio>1, the sum of an infinite series diverges (exponential growth), i.e. the sum becomes infinite; when the common ratio<1, the sum of an infinite series converges (exponential decay), i.e. the sum approaches a finite number.

example: exponential growth: What is the sum of the finite series 1+2+4+...+128 (hint: reordering the series to 128+64+...+1=111111112=0xff=ffh) [58] ? What's the sum of the finite series: 20+ 21+ 22+...+ 231 [59] ? What is the sum of the finite series 100+101+102 [60] ?

example: exponential decay: (from rec.humor.funny http://www.netfunny.com/rhf/jokes/08/Nov/math_beers.html) An infinite number of computer programmers go for pizza. The first programmer asks for a pizza, the next programmer asked for half a pizza, the next for a quarter of a pizza... The person behind the counter, before waiting for the next order yells out to the cook "x pizzas!" (where x is a number). What was the value of "x" (what is the sum of the infinite series 1+0.5+0.25+...) [61] ? The question rephrased is "what is the value of 1.111111..2?"

Let's derive the formula for the sum of a geometric series

sum = ar^0 + ar^1 + ar^2 + ... + ar^n

where r=common ratio (and r0=1); a=scaling factor. Multiplying all terms by r and shifting the terms one slot we notice

  sum = ar^0 + ar^1 + ar^2 + ... + ar^n
r*sum =        ar^1 + ar^2 + ... + ar^n + ar^(n+1)

Can you do something with these two lines to get rid of a whole lot of terms (this process is called telescoping)? Hint [62] . This leads to the following expression for the sum of a geometric series (for r!=1).

sum = a(r^(n+1)-1)/(r-1)

(For r<1, you can reverse the order of the two terms in each of the numerator and denominator.) Using this formula what is the sum of the finite series 1+2+4+8+...+128 [63] ?

From the formula, what is the sum of the infinite series 1+1/10+1/100+ ... [64] ? Note: the sum can be written by inspection in decimal as 1.111.... although it's not so obvious that this 1/0.9.

From the formula, what is the sum of the infinite series 1+2/10+4/100+ ... [65] ?

From the formula, show that the sum of an infinite series, with (r<1) is always finite [66] .

1.14. Geometric Progressions: chess and the height of a pile of rice

Note
The version of the story quoted here comes from George Gamow's book "One Two Three...Infinity". The book came out in 1953 and is still in print. It's about interesting facts in science and I read this book in early high school (early '60's). In 2008 I still find it interesting. When I first looked up this story, I saw it in the book on π (and not from Gamow's book), where I found that the grain involved was rice and I did my calculations below on the properties of rice. It seems that the original story was about wheat. Rather than redo my calculations, I've left the calculations based on the properties of rice (the answer is not going to be significantly different).

According to legend, the inventor of chess, the Grand Vizier Sissa Ben Dahir was offered a reward for his invention, by his ruler King Shirham of India. The inventor asked for 1 grain of rice for the first square on the board, 2 grains of rice for the 2nd square on the board, 4 grains for the 3rd square... The king, impressed by the modesty of the request, orders that the rice be delivered to the inventor. (For a more likely history of chess see history of chess http://en.wikipedia.org/wiki/Chess#History.) How many grains of rice will the inventor receive [67] ?

If the Royal Keeper of the Rice counts out the grains at 1/sec, how long will he take (in some manageable everyday unit, i.e. not seconds) [68] ? Clearly the inventor would prefer not to have to wait that long, but understanding exponential processes, it's likely that he'll suggest to the Royal Keeper of the Rice that for each iteration, that an amount of rice double that of the last addition be added to the pile (this is not part of the legend). How long will it take the Royal Keeper of the Rice to measure out the rice now [69] ? The lesson is that if you handle exponential processes with linear solutions, you will take exponential time (your solution will scale exponentially with time); if you have exponential solutions, you will take linear time (your solution will scale linearly with time). (Sometimes exponential solutions are available e.g. the factorial series for π, sometimes they aren't.)

How high is the pile of rice? First we'll calculate the volume. To find the height, we need to know the shape of the pile. The rice will be delivered by the Royal Conveyor Belt and will form a conical pile. The formula for the volume of a cone is V=πr2h/3. The only variable for a cone of grains, poured from one point above, is the angle at the vertex, which will allow us to find the height of the pile of known volume.

The height of the pile can be done as a back of the envelope calculation. It doesn't have to be exact: we just need to know whether the answer is a sack full, a room full, or a sphere of rice the size of the earth. We have the number of grains; we want the volume. There's some more data we need. We can lookup the number of grains in a bag of rice. Rice is sold by weight. Knowing the weight, we can calculate the number of grains/weight. However we want the number of grains/volume, so we need the weight/volume (the density) of rice. Going on the internet we find the missing numbers

  • number/weight. There are 29,000 grains of long grain rice in a pound (http://wiki.answers.com/Q/How_many_grains_of_rice_are_in_a_one_pound_bag_of_long_grain_rice). A pound is about 0.5kg, so that's 60kgrains/kg.
  • weight/volume: From Mass, Weight, Density or Specific Gravity of Bulk Materials (http://www.simetric.co.uk/si_materials.htm) the relative density (specific gravity) of rice is 0.753. Density relative to what? Water. Water (by definition) has a density of 1kg/litre=1000kg/cubic metre. The density of rice then is 753kg/m3 (a bit less than a ton/m3). Objects, with a density less than water, float (in water), while objects with a density more than water will sink. As a check on this value for the density, does uncooked rice sink or float when you throw it into a saucepan of water? What's going on? Here's my explanation [70] .

We want volume/number; we have the weight/volume (let's say relative density is 0.75) and the number/weight (60,000/kg). How do we get what we want? Using dimensional analysis, the dimensions on both sides of the equation must be the same.

volume/number= 1.0/((number/weight)*(weight/volume))	#the dimension of weight cancells out
units?       = 1.0/( number/kg     * kg/m^3 )
	     = 1.0/( number/m^3 )
	     = volume/number

volume/grain = 1.0/(60000*750)

The units of our answer are going to be in the units of the quantities on the right, i.e. m<superscript>3</superscript>/grain
pip:# echo "1.0/(60000*750)" | bc -l
.00000002222222222222	#m^3/grain

Let's turn this into more manageable units, 
by multiplying by successive powers of 10^3 till we get something convenient.

pip:# echo "1.0*10^9/(60000*750)" | bc -l
22.22222222222222222 	#nm^3/grain (nano cubic metres/grain)

Can we check this number to make sure we haven't gone astray by a factor of 103 or so? What is the side of a cube of volume 1 n(m3) (this is a cube of volume 10-9 cubic metres, not a cube of side 1nm=10-9m.)? The length of the side is cuberoot(10-9m3)=10-3m=1mm. A rice grain, by our calculations, has a volume of 22 cubes each 1mm on a side. Does this seem about right? We could make the volume of a grain of rice, by lining up 22 of these volumes in a line (11mm*1mm*1mm) or 20 of them by lining up 5mm*2mm*2mm. This is about the size of a grain of rice, so we're doing OK so far.

We've got 264 of these grains of rice. What's the volume of the rice [71] ? What's the weight of this amount of rice using density=0.75tons/m3 [72] ?

The next thing to be determined is the shape of the cone. Mathematicians can characterise a cone by the angle at the vertex. For a pile of rice (or salt, or gravel), in the everyday world, the angle that's measured is the angle the sloping side makes with the ground (called the angle of repose http://en.wikipedia.org/wiki/Angle_of_repose). If the rice is at a steeper angle, the rice will initiate a rice-slide, restoring the slope to the angle of repose. A talus (scree) slope is a pile of rocks at its angle of repose. A talus slope is hard to walk up, as your foot pressure pushes on rocks which are at their angle of repose. Your foot only moves the rocks down the slope and you don't get anywhere (see the photo of scree http://en.wikipedia.org/wiki/Scree). An avalanche is caused by the collapsing of a slope of snow at its angle of repose. A sand dune (http://en.wikipedia.org/wiki/Dune, look at the photo of Erg Chebbi) in the process of building, will have the downwind face at the angle of repose for sand (this will be hard to walk up too).

Rice still with its hulls forms a conical pile with an angle of repose of about 45° (for image see rice pile crop http://www.sas.upenn.edu/earth/assets/images/photos/rice_pile_crop.jpg). For hulled rice (which the inventor will be getting) the angle of repose The Mechanics and Physics of Modern Grain Aeration Management is 31.5° or Some physical properties of rough rice (Oryza Sativa L.) grain. M. Ghasemi Varnamkhastia, H. Moblia, A. Jafaria, A.R. Keyhania, M. Heidari Soltanabadib, S. Rafieea and K. Kheiralipoura 37.66 and 35.83°.

For convenience, I'm going to take an angle of repose for rice of 30°. What is the relationship between the radius and height of a cone with angle of repose=30°?

We need to take a little side trip into trigonometry. Come back here when you're done.

The formula for the volume of a cone (V=πr2h/3) involves the height and the radius. When you make a cone by pouring material onto the same spot, the height and radius of the cone are dependant (i.e. they are always determined by the angle of repose). In this case, we can use a formula for the volume of a cone that uses either the radius or the height, together with the angle of repose.

If we know the angle of repose, what is the trig function that tells us the ratio of the height to the radius of the cone [73] ? Let's say tan(angle_repose)=0.5 then

h = tan(angle_repose)*r
h = 0.5*r

substituting this relationship into the formula for the volume of a cone gives
V = pi*r^2*0.5*r/3
  = 0.5*pi*r^3/3

in general terms
V  = pi*tan(angle_repose)*r^3/3 

What is the ratio height/radius for a cone with an angle of repose of 30° (you can do this with a math calculator or with python)? Python uses radians for the argument to trig functions. You turn radians into degrees with degrees() and degrees into radians with radians().

dennis:/src/da/python_class# python
Python 2.4.3 (#1, Apr 22 2006, 01:50:16) 
[GCC 2.95.3 20010315 (release)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from math import *
>>> radians(30)
0.52359877559829882
>>> degrees(0.52359877559829882)
29.9999999999999996	#note rounding error in last place in real representation
>>> tan(radians(30))
0.57735026918962573
>>> 

For a cone of rice, h/r=0.577. For a back of the envelope calculation we can make h/r=0.5 (or even 1.0; it's not going to make much difference to the height of the pile of rice). If you want a more accurate answer, you can come back later and use the real numbers. What angle has tan(angle)=0.5 [74] ? It's somewhere between 26 and 27° The inverse trig functions allow you to find the angle for which a number is the tan(). The inverse of tan() is called either tan-1(), arctan() or atan() (the name in python).

>>> atan(0.5)
0.46364760900080609	#angle in radians
>>> degrees(atan(0.5))
26.56505117707799	#angle in degrees

giving an angle of repose of 26.5°

The cone of rice, from its angle of repose (in the section on trigonometry), has r=2h. For such a cone V=πr2h/3=(4π/3)h3. If the rice is presented to the inventor as a conical pile of rice, what is its height [75] ? For comparison, how high do commercial airliners fly [76] ? How high is Mt Everest [77] ?

The radius of the cone of rice with an angle of repose of 30° is twice the height. What is the diameter of the base of the pile of rice [78] ?

What is the world's annual production of rice (look it up with google)? How many years of the world's rice production would be needed for the reward [79] ?

1.15. Geometric Progressions: Time for the Moore's Law aided calculation of e=(1+1/n)n

Let's go back to the program which calculated the time to determine the value of π, when the computer speed increased with Moore's Law. The number of iterations/yr is a geometric series with scaling factor=107.5*109=1016.5, common ratio=2. You didn't need to write a program to calculate the sum. Using the formula for the sum of a geometric series, what is the sum of n terms of this geometric series [80] ? What is n if sum=10*100? You will need a calculator - python will do. Make a few guesses about x to calculate values for 2**x/10**83.5 which in turn will give the expected number of iterations. Here's my calculation [81] . The answer is that you will need 277 years of calculating, with the value for π arriving at the start of the 278th year.

Note

If you know logs:

pip:~# echo "83.5*l(10)/l(2)" | bc -l
277.38

ie
10**83.5=2**227.4

(I haven't been clear about the fencepost problem. You now need to decide if the answer is 277 or 278 years.)

1.16. Geometric Progressions: creating money by multiplication

Let's differentiate wealth and money

  • wealth: a society has wealth when it has educated, skilled and healthy citizens, public transport, hospitals, schools, libraries, roads, food, housing, jobs, a functioning and trusted legal system and government, a stable economy, manufacturing plant, peace and a low crime rate.
    Note
    In the US, houses are not great investments and their financial value ($) is easily manipulated. For housing to be regarded as wealth, the houses have to be designed to last several centuries, zoning laws have to be stable, allowing build up of shops within walking distance and a train system to service the residents. When houses are part of a long lasting and stable infrastructure, then they're wealth.
  • money: money is a form of exchange for goods and services.

Where does money come from? As Galbraith says ("The Age of Uncertainty, John Kenneth Gailbraith (1977), Houghton Miflin, ISBN 0-395-24900-7 - anything by Galbraith is worth reading, not only for the substance, but for the style in which he communicates) on p 167, it's created from nothing.

Galbraith credits the mechanism for the creation of money to the Amsterdam bankers (bottom of p 166) in 1606, although the principle was known earlier. The theory of the creation of money (multiplication) is a standard part of Macroeconomics. The best description I found on the web is at The Banking System and the Money Multiplier (http://www.colorado.edu/Economics/courses/econ2020/section10/section10-main.html), which I'm using here.

Banks make loans, from which they earn interest. This is obvious to everyone. In the process, banks also create money. How this works is not quite so obvious. Let's assume that everyone banks in the same or linked banking system (for convenience, let everyone bank at the same branch of a bank). Warren Buffett deposits 1M$ in the bank. The bank has an asset of 1M$ and a liability to Warren Buffett of 1M$. Let's look at the bank's balance sheet.

Programmer's Bank of N.C.

  Assets	                 Liabilities
  D    C                          D    C
-----------                      -----------
 1M  |       cash                    | 1M    Warren Buffett
Note
Double entry accounting rules: Each block here is called an account. In this case, the bank has an asset account and a liability account. When money moves, the same amount of money must appear in two accounts, in opposite columns. In accounting, the left hand column is called "debit" and the right hand column "credit". These two names are just opposite labels, like positive and negative, up and down, left and right. You always have credits and debits in equal amounts. In accounting money is not created or destroyed. It just moves. For more about accounting see Gnucash and Double Entry Accounting (http://www.austintek.com/gnucash/).

The bank has 1M$ on hand in cash and has a liability to (owes) Warren Buffett for 1M$.

Note

The bank's net worth is $0 However they have 1M$ cash on hand, which they can use to make money. As long as the bank makes enough money to pay Warren Buffett his monthly interest (which will confirm his wisdom in choosing to leave his money with this bank), the bank stays in business. Warren Buffett is in no hurry to close out his account (retrieve his money).

Surprisingly much of the economy (people, businesses) has little net worth or is in debt and everyone has agreed that this is just fine. To buy my house, I started off with a 5% down mortgage, where I was in debt to the tune of 95% of the value of my house. Many businesses have receive and pay out money at different rates throughout the year; sometimes having lots of cash and sometimes having less than none. These businesses have a line of credit with a bank, to even out the money flow, allowing them to make payroll and expenses at all times of the year. Of course you have to pay interest on the loans, but it makes the business possible, and allows people to buy a house rather than rent.

Let's say the bank makes some really bad decisions and looses all of Warren Buffett's money. Who's in trouble? Not the bank - they have no money. What about the bank's officers? Their houses and personal money aren't involved in the bank. Only Warren is in trouble. He's lost 1M$. Admittedly, the bank's officers won't be able to show their faces in town (they'll leave), but that's about as bad as it gets for them. In the US there's no moral outrage about this sort of thing. The bank's officers, using the same skills they used to convince you to deposit your money with them, will get jobs somewhere else. To handle this situation, in the US, the FDIC insures the depositor's money, so that in the event of a bank forfeiture, the FDIC will reimburse you for the lost amount. For this to work, the FDIC has to keep tabs on the bank's accounts and track what they're doing. The bank has to pay the FDIC for their work, and so that the FDIC has enough money to handle any forfeitures (i.e. insurance). Once this has been setup, the bank can hang the FDIC sign in their window. In the last few years, with no forfeitures since the S&L scandal (only 20yrs ago), Congress decided to stop levying this charge against the banks. (Congress and the banks were good mates you understand, and the banks long ago had stopped being bad boys. Congress wanted to help the economy with reduced regulation, which as everyone knows, increases honesty and transparency in business.) As a result, with the credit and bank implosion in late 2008, the FDIC found it had no money socked away to handle the bankruptcies. Where did the money to handle the defaults come from? Congress took it from the taxpayers (definitely not the banks).

What's the bank going to do with Warren's money? If they just let it sit in the vault, there won't be any income for the bank. The bank has to pay salaries, expenses, and to Warren Buffett, interest. To cover expenses and earn money, the bank loans out the money for which it charges interest. They don't loan all of it out. In the US, the Federal Reserve (the "Fed") decrees that the bank must hold 10% of their deposits in reserve, against possible withdrawals by customers. The bank deposits the required amount with the Federal Reserve (earning a small amount of interest). Let's look at the bank's balance sheet now.

Python Programmer's Bank of N.C.

  Assets	                 Liabilities                   Federal Reserve 
	                         (deposits)                        Escrow 
  D    C                          D    C                           D   C
-----------                      -----------                   -------------
 1M  |       cash                    | 1M    Warren Buffett          |
     |  0.1M                         |                          0.1M |    

The bank has 0.9M$ available to make loans. For simplicity, let us assume that the entire $900,000, made available by Warren's deposit, was borrowed by one business: Joe the Plumber. Joe the Plumber doesn't borrow the money for fun, he wants to invest in new capital equipment, to increase the productivity of his workers. Joe the Plumber wants to invest in a new laser rooter, for which he pays $900,000 to Sapphire Slick's Laser Emporium. Let us also assume that Sapphire has an account with the Python Programmer's Bank, where she deposits the $900,000 received from Joe the Plumber's purchase. The bank deposits 10% of Sapphire's deposit with the Fed. Here's the accounts at the Python Programmer's Bank after Sapphire's deposit.

Python Programmer's Bank of N.C.

  Assets	                 Liabilities                   Federal Reserve         Loans
	                         (deposits)                        Escrow 
  D    C                          D    C                           D   C               D    C
-----------                      -----------                   -------------         -----------
1.0M |                               |1.0M Warren Buffett            |                   |
     |  0.1M                         |                          0.1M |                   |
     |  0.9M                         |                               |              0.9M |   Joe the Plumber
0.9M |                               |0.9M Sapphir Slick             |                   |   
     |  0.09M                        |                          0.09M|                   |   

Total:
0.81M                                 1.9M                      0.19M               0.9M

The bank now has assets of 0.81M$ (with 0.19M$ in the Fed escrow account). It has issued a loan for 0.9M$ to Joe the Plumber (there is now 0.9M$ injected into the business world). However the bank now has deposits of 1.9M$ (it also has liabilities of 1.9M$). The bank has an extra 0.9M$ on deposit, which it can use to make more loans. Let's say the bank now issues a loan of 0.81M$ to another business, which buys equipment or has work done for it and the contractor deposits the money back in the Python Programmer's bank. And this goes on till the bank's assets are reduced to 0$. How much money is on deposit at the bank, how much money is in the Fed escrow account and how much money has been loaned out to businesses?

Loans (M$): 
0.9 + 0.81....
= 1(0.9^1+0.9^2+...)
using the formula for the sum of a geometric series
S=a(1-r^n)/(1-r)
= 0.9 * (1-0.9^inf)/(1-0.9)
= 9.0M$

The amount of money that's loaned out is the original amount of money put into circulation by the first loan, divided by the Federal Reserve ratio (the 1-0.9 term).

the money multiplier = 1/reserve_ratio

The amount of money on deposit is 10.0M$ (9.0M$ + original deposit), and the amount of money in the Federal Reserve is 1.0M$.

Note
The bank has no money as reserves (in case anyone wants to withdraw money) (the bank will usually not lend out all its money).

Here's the bank's accounts when all of the money has been loaned out

Python Programmer's Bank of N.C.

  Assets	                 Liabilities                   Federal Reserve         Loans
	                         (deposits)                        Escrow 
  D    C                          D    C                           D   C               D    C
-----------                      -----------                   -------------         -----------
1.0M |                               |1.0M Warren Buffett            |                   |
     |  0.1M                         |                          0.1M |                   |
     |  0.9M                         |                               |              0.9M |   Joe the Plumber
0.9M |                               |0.9M Sapphir Slick             |                   |   
     |  0.09M                        |                          0.09M|                   |   
     .                               .

Total:
0.0M                                  10.0M                      1.0M               9.0M

Notice we started with a deposit of 1M$ and when the process is finished, the bank has 10M$ in deposits, with 9M$ in loans and 1M$ in the Federal Reserve. Was any wealth created [82] ? Was any money created [83] ?

In reality there are a number of leakages from the above scenario, that will reduce the value of the multiplier:

  • People may not deposit all of their cash into the banking system. Besides the money we keep in our wallets, we may save some of our money outside the depository banking system.
  • People may decide not to spend all our money.
  • Banks may not loan out all potential reserves, choosing to keep excess reserves.

The multiplier effect works in all sectors of the economy. You buy a self levitating mouse for your computer; the clerk at the store, the store's owner, the truck driver who delivered the mouse, the factory that made the mouse, the guy that designed the mouse, all get money. These people in turn go to stores and buy Homer Simpson towels... and on it goes. The circulation of money maintains the economy (at least in the US) and leads to Fisher's concept of the velocity of money Quantity theory of money (http://en.wikipedia.org/wiki/Quantity_theory_of_money), which is a bit outside the scope of this course.

What's needed to maintain this system of creating money

  • Banks can't let their reserves drop to $0 (they can't lend out all their money), or there'll be nothing when the customer who have money on deposit come in the door.
  • Customers can only retrieve their money one at a time. A run on the bank, usually caused by people thinking that the bank is in trouble (it may be in trouble), will quickly exhaust the bank's reserves.
  • People have to spend their money and not save it.

As Galbraith says on p167 in the above reference

The creation of money by a bank is as simple as this, so simple, I've often said, the mind is repelled.

The important thing, obviously, is that the original depositor and the borrower must never come to the bank at the same time for their deposits - their money. They must trust the bank. They must trust it to the extent of believing it isn't doing what is does as a matter of course.

While we may think of banks as just safe places to store our extra money, their main role in the economy is to make loans. If they stop making loans, the amount of money in circulation drops by a factor of 10. Credit worthy people can't get loans to buy a house, people who move and want to sell their houses can't, but still have to keep paying the mortgage on their empty house, businesses can't meet payroll or pay for the goods they need to buy to stay in business. The economy goes into a death spiral, with people being laid off and defaulting on their mortgages, businesses fail, people default on their loans and the banks have even less money to make loans.

The multiplication feature of the creation of money means that any small changes in the economy are greatly magnified. If people stop spending money, or banks stop making loans, the amount of money in circulation drops drastically. Businesses can't pay their bills, people loose their jobs and can't pay their mortgage, which means that the banks loose money on their bad loans and can't make more loans. Then in good times, the economy swings the other way. For people trying to go about their lives, as my friend Tony Berg says, the worst thing is change. You can plan your life if you know the rules, even if they aren't great rules, but if one day after making all your plans, someone comes and says "we've changed the rules, the multipliers, the interest rates...", your plan goes out the window. The multiplication factor and the amount of money in circulation needs strict monitoring, or the economy will go through booms and busts, causing havoc and disruption in the lives of innocent people, who are just trying to work hard and get their jobs done. When you hear newspapers/commentators talking about the US economy being a vibrant innovative place, where people can take risks and reap the rewards, what they're talking about is an economy with large swings where a small number of people (those with the money) can make a packet and get out, while the expendable workers suffer lives of great disruption. However the newly unemployed will have a chance to be the first to buy the next iPod.

One of the critical features of this system is the reserve ratio (some countries, e.g. Australia, don't require banks to deposit any money with their equivalent of the Reserve Bank). The banks are always pressing to reduce the amount of money they have to deposit with the Fed. If this is reduced to 5%, then the banks can loan out twice the amount of money, and earn twice as much interest. The problem with having lots of money to make loans, is that there may not be enough demand for the extra money from credit worthy people, in which case the banks will have to start chasing people who are less credit worthy. This got a lot of banks into trouble in the US credit crunch of 2008.

The instability in the economy, which is part and parcel of the banks being able to create money out of thin air, has led some economists e.g. Soddy (a Nobel Prize winning chemist turned economist) to propose that banks not be allowed to create money at all (see Op-Ed in NYT Mr. Soddy's Ecological Economy). Of course such radical statements are laughed at by the economists who profit handsomely from the large bonuses they receive from making all the loans, and who are bailed out by the tax payers when the loans all collapse.

We're all told that saving is a good thing (a penny saved is a penny earned etc - who said this [84] ?). Note that personal saving derails the system here, by taking money out of the multiplication spiral. Economics text books are full of examples of the type "If you do X, what effect will this have on you, have on the economy? If everyone does X, what effect will this have on you, have on the economy?" In some of these an activity done by one or by everyone is good (e.g. working hard), while another activity (e.g. preparing a good resume, so you will get a particular job) is good for you, but you wouldn't want to hand around your resume to all the other applicants or they would figure out what's wrong with theirs. You need to know what's good for you and what's good for the economy. Sometimes they're the same; sometimes they're the opposite.

It turns out that saving is good for you, but just not good for the economy, at least in the sense discussed above. After 9/11, George Bush told the US to go shopping (to keep the economy rolling), when he should have told the country to tighten its belt.

The US credit crunch of 2008 was caused by banks making home loans to people who didn't have a good enough credit rating for standard (prime) rate mortgage. Instead these people were given a sub-prime mortgage, with a higher interest rate to handle the risk that the mortgagee would default. The only reason that lending money to high risk mortgatees worked was because at the same time there was a house price bubble (house prices were rising faster than their real worth). When people defaulted, the increased value of the house covered the bank's costs. As well the defaulter would get back some money from the sale of the house. Previously no-one would touch these high risk people, but with the house prices rising, some banks decided it was worth the risk and these banks made a packet writing sub-prime mortgages. Eventually the house price bubble burst. When house prices decreased with the purchase value of the house, and mortgagees defaulted, banks with outstanding loans now had a lot of bad loans and the mortgagees went bankrupt.

The initial problem, in the credit crunch, was that banks no longer had money to make loans. Businesses who weren't a part of the sub-prime mortgage problem, weren't able to make their payroll, make planned purchases, even though their businesses were being conducted to the highest standards. The problem became obvious to people who read newspapers in the middle of 2007.

Here are the players involved

  • Congress (and their appointees, the Secretary of the Treasury etc). The people have the job of regulating the greed of human nature so that the financial industry does not rip people off, or cripple the economy, in its rush to exercise new innovative financial instruments.
  • Banks who made a packet of money issuing risky loans during a house price bubble, and who now had a load of bad loans. The banks had "insured" these loans with innovative, opaque and unregulated new instruments called credit default swaps and CDOs (go look them up if you want to know more). The point of reserving money for insurance, is that it must be safe somewhere, in conservative investments. The banks instead used their "insurance" money on more sub-prime mortgages. The banks were screaming for help on the tried and true principle of capitalism, that if you're in trouble, you get the taxpayers to bail you out, but if things are going well, you're on your own (this is called socialism saving capitalism).
  • A bunch of people who should not have been allowed to take out ARMs ( Adjustable Rate Mortgage. [85] ) and who, if had been given "truth in advertising" advice, would never have taken them. There is the principle of "moral hazard" which says that if people know you'll bail them out when they engage in risky behaviour, then people will be encouraged to take more risks. However you can only morally not help someone, if they've engaged in risky behaviour in full knowledge of what they're doing. It's for this reason that the government requires testing of pharmaceuticals, safe and regulated designs of cars, roads and airplanes. It is clear that the people in the lower rungs of the economy weren't fully appraised of the consequences of their actions. The other problem is that people live in neighbourhoods of similar economic levels. Once people started defaulting on their mortgages in a neighbourhood, the price of the other houses fell, putting other responsible people in financial stress, eventually leading to whole neighbourhoods in terrible financial circumstances.
  • Normal financially frugal and responsible people going about their daily business, who couldn't get loans anymore and consequently whose lives and businesses were going down the tubes.

The people who were running the economy about Sept 2008 realised there was a problem. (these were Henry Paulson and Ben Bernanke. Alan Greenspan had retired and expressed profound disappointment that his perfectly working scheme had been derailed by human greed. Quite reasonably, Greenspan was not held responsible for human greed.)

With what you know now, you're qualified to solve the problem. To make it easier, I'll make it a multiple-guess quiz. What would you do (you can choose more than one answer)? While you're at it, what do you think Congress did?

  • Give 700G$ to the financial industry to pay off their bad loans, without any requirements to fix any of the problems, or to start making loans again. Allow the businesses to use the money to put themselves into a better position to emerge from the financial carnage (e.g. buy up other businesses). Allow the senior staff to keep 100M$ in bonuses (for their continuing excellent work), despite 3 or 4 quarters of continuing losses. Tack large amounts of pork onto the bill, so that everyone in Congress will vote for it.
  • Remind the financial industry that the rules had been setup at their request to remove the odious burden of regulation. The financial world was just having a minor correction and that everything was still working perfectly. They should use the reserves they'd accumulated in the good times to carry them through.
  • Write new rules to make sure that the problems above would never happen again. Use the taxpayer's money to start issuing loans. If the banks weren't going to make loans, then the government can do it.
  • Many solutions were offered to handle the people who should never have had loans. I presume most of them would have worked. One was for the goverment to pay the increase in interest rate on the ARMs for the next 5 yrs, by which time the economy should be in better shape, their house price would be better, and the people would have 5yrs notice that they would have to get out if they didn't get their act together.

Even though Paulson and Bernanke watched mesmerised as the whole economy sank, the US Congress in Oct 2008, decided that they were the best people to fix the problem and gave them 700G$ of the tax payers money (how much more does each person in the US owe the government as a result of this decision [86] ?) to do whatever they liked to fix the problem. The plan was to give this money to the banks who were in trouble to use in whatever way they wanted. It was clear to even the uneducated, that there was no hope that this would work. The banks used the money for weekend parties and retreats, to pay off debts to other banks and to give bonuses to the executives. Guess how much of the 700G$ that the goverment required to be used for loans [87] ? After receiving 85G$ from the taxpayers, AIG held a week long party at an expensive resort (AIG) costing 500k$ (see AIG bailout means facials, pedicures http://blogs.moneycentral.msn.com/topstocks/archive/2008/10/07/aig-bailout-means-facials-pedicures.aspx). to make sure that the taxpayers know the contempt in which they are held by the financial industry. Not wishing to miss the party, the Detroit auto makers arrived in private jets to plead their special poverty. Six weeks later (mid Nov 2008), after spending 350G$ of the money without the slighest effect, Paulson admitted that the plan wasn't working. He was adamant that the money should be used to bailout the banks (an investment) rather than prevent mortgage forclosures (an expense). Paulson changed tactics - he would send the remainder to credit card companies. How much money was going to people who needed loans [88] ? This is being written in mid Nov 2008, when the results of this new move aren't in. My prediction, as a newspaper reading non-economist, is that the new plan will not work any better than the earlier plan.

If you're like me when I was growing up, you'd say "these are a bunch of idiots. I can see what's wrong already, I'm going to have no problem getting a job, kicking these people out and fixing the whole situation up." You're wrong.

The futility of trying to establish a meritocracy can be seen in the failure of Plato's "The Republic". If you're good in school, your elders and betters will say "we want bright energetic people like you". They'll give you prizes at the end of the year. You'll wind up is working for them, in an assembly line (you may not be assembling cars, but you'll be in an assembly line none the less). The pinacle of achievement is a Ph.D. You work for 5yrs, at no pay, to help your supervisor, who will get tenure out of your (and other student's) work. At the end of it, you get a piece of paper which tells the world "this person will work for 5 yrs at no pay, for a piece of paper saying that they're really smart". Employers love to see people like this come in the door for job interviews. Ask your professors for advice in University: "of course you should take my course; there's a bright future in this field, otherwise I wouldn't be in it". Don't ask anyone with a vested interest in the outcome, for advice (OK ask them, but be careful of taking it). While a Ph.D. is a requirement for entry into many fields, make sure that the time spent on it is on your terms and not just to help the career of others.

So why won't you get Paulson's job? You know what's wrong, know what needs to be done and you know that they're a bunch of idiots. These people see you coming. They have comfortable jobs, they aren't held accountable for their blunders and aren't required to deliver results. Their pronouncements are greeted with fauning and adulation by Congress. Are they going to be glad to see you walk in the door [89] ? You'll be greeted with the same applause that's followed you since grade school. They'll find something special for a person like you and you won't realise till long afterwards, that you wasted your time. They'll do everything they can to crush you from the very start. If you want to fix up the situation, you'll have a life like Martin Luther King Jr, Jonas Salk or Ralph Nader.

Financial people who made it, not playing along with the system, but using its weaknesses for their own profit, include Warren Buffet and Jeremy Grantham, both of whom seem to have had comfortable lives.

Early on you'll need to choose between the life of Henry Paulson and Alan Greenspan, with public adulation on one hand, or the life of Jonas Salk on the other hand, who cured the world of polio, but who was regarded as a technician by the scientific community, and who had to raise all the money for his cure himself (through the March of Dimes). Salk wasn't even admitted as a member of the National Academy of Science (neither was the educator and populariser of science Carl Sagan). Richard Feynman resigned from the Academy, seeing it as a club of self congratulatory incompetents (my words not his).

If you want to set things right, competence in your field is necessary, but not sufficient. The frontier will be the idiots, not your chosen field of endeavour. As Frederick Douglass said

"Power concedes nothing without a demand. It never did and it never will."

1.17. Parallel Programming and Distributed Denial of Service (DDoS): linear or exponential?

As I said earlier, if you have problems that scale exponentially, to solve them in linear time, you need tools that scale exponentially. If your tools scale linearly, you need exponential time to solve the problem.

One attempt to increase the amount of computing power is parallel programming. You divide a problem into smaller pieces, send each piece off to its own cpu and then to merge/join/add all the results at the end. It turns out that dividing problems into subproblems and joining the partial results doesn't scale well. Much of the time of the computation is spent passing partial answers back and forth between cpus. As well the cost of cpus scales linearly (or worse) with the number of cpus. So parallel programming is a linear solution, not an exponential solution.

When a client on the internet connects to a server on the internet, the client first sends a request to connect (called a SYN packet). This is the tcpip equivalent of ringing the door bell (or the phone). The server replies (SYN-ACK) and the client confirms the connection (ACK). The client then sends its request (give me the page "foo.html"). A DDoS is an attack on a machine (usually a server for a website), where so many (network) packets are sent to the server, that it spends all its time responding to DDoS packets (answering the door to find no-one there), and is not able to respond to valid knocks on the door. The machine doing the DDoS only sends the SYN packet. The server replies with the SYN-ACK and has to wait for a timeout (about 2mins) before deciding that the client machine is not going to connect. There is no way for the server to differentiate a DDoS SYN packet from a packet coming from a valid client on the internet.

DDoS machines are poorly secured machines (Windows) than have been taken over (infected with a worm) by someone who wants to mount an attack on a site. The machines are called zombies and once infected, are programmed to find other uninfected machines to take over. The process of infecting other machines is exponential or linear [90] ? The zombies sit there appearing to be normal machines to their owners, until the attacker wants to attack a site. The attacker then commands his zombies (which can number in the millions) to send connect requests to a particular site, taking it off the network. All the zombies in the world are Windows boxes linked to the internet by cable modems. It takes about 20 mins after an unprotected Windows box is put on the internet for it to be infected by one of these worms.

1.18. Lucky Lindy

Note

For the information on the navigation required for this feat, I am indebted to "Portnoy's Imponderables", Joe Portnoy, 2000, Litton Systems Inc, ISBN 0-9703309-0-1. available from Celestaire (http://www.celestaire.com/). Table 4 on p 33 showed how lucky Lindy was. This book has tales of great navigation for those who have to sit at home, while everyone else is out in their boat having the real fun. Celestaire has all sorts of nice navigational equipment.

Other information comes from Lindbergh flies the Atlantic http://www.charleslindbergh.com/history/paris.asp), Charles Lindbergh, (http://en.wikipedia.org/wiki/Charles_Lindbergh), Orteig Prize (http://en.wikipedia.org/wiki/Orteig_Prize), Charles Lindbergh (http://www.acepilots.com/lindbergh.html) table of full moons (http://home.hiwaay.net/~krcool/Astro/moon/fullmoon.htm)

Following a run of bad weather, a forecast on 19 May 1927 predicted a break in the rain. At 7:20AM on 20 May 1927, 4 days after the full moon, an unknown air mail pilot, Charles Lindbergh, in pursuit of the $25k Orteig Prize for the first transatlantic flight between New York and Paris, slowly accelarated his overloaded (2385lb gasoline) single engine Ryan monoplane, northward down the boggy runway at Roosevelt field, clearing the power lines at the end of the runway by only 20'. To save weight Lindbergh carried no radio, no sextant and only carried a compass (accuracy 2°) and maps for navigation (but presumably having an altimeter and airspeed indicator). The plane had no brakes, and the plane was so filled with fuel tanks that Lindbergh had to view the world outside through a periscope.

Six people has already died in attempts on the Orteig prize. Two weeks earlier, Nungesser and his navigator, who planned to fly through the moonless night, left Paris in an attempt to reach New York and were not seen or heard of after they crossed Ireland. Six months earlier, leaving from the same Roosevelt field, Fonck and two crew members, never got off the ground, when the landing gear of the grossly overloaded (by 10,000 lbs) transport biplane collapsed during takeoff. The two crew members (but not Fonck) died in the subsequent inferno.

Flying air mail may seem humdrum compared to the barnstorming and wingwalking popularised in the history of the early days of flying. The job of air mail pilot may remind you of the postman who delivers your daily mail.

In fact air mail pilot was a risky job. Flying was all weather, minimal navigation equipment and with bad airfields. Following a strike in 1919 over safety a new procedure was instituted "if the local field manager on the Post Office Department ordered the pilots to take off in spite of their better judgement, he was first to ride in the mail pi t (a kind of second cockpit) for a circuit of the field".

Whatever it was that was so vital to transport quickly, it was delivered at a high cost. Life expectancy for a Mail Service pilot was four years. Thirty one of the first forty pilots were killed in action, meeting the schedules for business and goverment mail. The demand lead to the Night Transcontinental Air Mail Service, using bonfires lit across the country and flare pots at the landing fields. One in six air mail pilots was killed in the nine years of the service, mostly from flying through bad weather.

In contrast today a commercial airline pilot pays normal life insurance rates.

Condensed from "Normal Accidents", p125, Charles Perrow (C) 1999, Princeton University Press, ISBN-10 0-691-00412-9

Lindbergh made landfall at Nova Scotia, only 6 miles off-course and headed out into the Atlantic as night fell. Lindberg had marked up his Mercator Projection map into 100mile (approximately 1hr flying time) segments of constant magnetic heading. He hand pumped fuel from the tanks inside the plane to the wing tanks. He alternately dodged clouds, which covered his wings with sleet (what's the problem there [91] ?) by flying over them at 10,000 ft or going around them. At one stage Lindbergh thought of turning back, but once half way, it was easier to keep going. His hope to use the full moon for illumination, did not pan out (what's the problem here [92] ?). To gauge windspeed, Lindbergh flew 20' over the wavetops, looking at the direction of spray and intensity of the whitecaps (how does this help [93] ?).

Sunrise on the second day came over the Atlantic with Lindberg arriving over the southern tip of Ireland in the late afternoon, after 1700miles and almost a day of ocean, only 3 miles off-course, a incredible directional error of only 0.1°. The adverse weather had put him an hour behind schedule, and considering the errors in estimating drift from the waves, magnetic variation and the inherent error in the compass, he should have been 50 miles off course. He crossed into Europe as the second night fell, arriving in Paris at 10pm. Amazingly his imminent arrival had been signalled from Ireland, with 150,000 people waiting at Le Bourget field to greet him.

Considering there was no internet, few phones or cars back then, Paris notified and moved 150,000 people by public transport in a few hours, a feat that be impossible in most American cities even today. Would you believe your neighbour pounding on the door "Vite! Lindbergh arrivera au Bourget a 2200hrs!" while you're relaxing having dinner and reading Le Figaro? No way. I would have stayed at home. My neighbour would have fumed (in a John Cleese voice) "You stupide Frenchman!"

The whole world went gaga. Lindbergh was a hero. In my lifetime, the big event was Armstrong landing (and returning) from the moon. But all of us watching the great event on TV knew we wouldn't be landing on the moon. With Lindbergh's flight, people realised that anyone would be able to fly across oceans.

Lindbergh was aggrieved by the considerable number of people who declared his accomplishment just luck. Let's see how lucky he was. As a person who's read just about every story of adventure and exploration that's been printed and has spent a large about of time hiking in the outdoors, you need to know the difference between a successful trip/expedition and an unsuccessful one:

  • in a successful trip, nothing goes wrong, there are no great tales to tell and the whole thing is no big deal. Most people don't hear about these trips; "we went out, had a good time and came back" is not going to be in the newspapers.

    How little do we know of Amundsen's routine trip to the south pole? Amundsen trained for years, overwintering near the north pole, learning from the Innuit and bringing with him the best Greenland dogs for hauling. His party travelled on skis. No-one's interested in his logs ("day 42: marched another 50km. Sven's turn to prepare dinner. Dogs in good shape. Slept well.").

  • in a unsuccessful trip, people are lost or die or suffer great privation. There is great heroism (or cowardice) on the part of some people. These trips make great stories (as long as you weren't on the trip) and everyone hears about them.

    Everyone can recite details from Scott's blunder to the pole, in which all members of the trip died of poor planning. Scott thought that being British would be enough to get him to the South Pole and back safely. His party were unfamiliar with dogs, initially tried horses and eventually man-hauled the sleds. They weren't prepared for the resulting increased perspiration which iced up the insides of their clothes. Scott's party travelled by foot (no skis) and were injured by falls into crevasses, which Amundsen's party on skis more easily passed over. Everyone has read the descent into disaster seen Scott's logs, with his demoralised entry on arriving at the pole to see Amundsen's flag "Great God! This is an awful place".

    In Shackleton's 2nd trip to the south pole, due to lack of funds, he took a ship, the Endurance, whose hull would not survive being frozen in. Shackleton expected to be able to land his crew and get his ship out before the sea iced over. Instead the Endurance is caught and crushed in ice (http://www.shackleton-endurance.com/images.html) before they reached land. The trip is better known than others because of Frank Hurley's photographs. (Shackleton knew that he'd never be able to pay for the trip without them.) There is no doubt of Shackleton's leadership and heroism, however the underfunded trip was doomed from the start.

    In a war, there are more medals issued for a calamitously executed battle than for a clean victory.

What determines whether the trip is going to be successful or not? The difference between a successful trip and an unsuccessful trip is (ta-dah... drum roll): good planning and preparation. That's it - that's all you need to know. Most of the dramatic adventures that are burned into society's conciousness about how we got to be where we are, the nature of bravery and heroism, and what we pass on to the next generation as important lessons, are nothing more than examples of really bad planning. The un-newsworthy successful trips that should be the focus of our learning are ignored.

To see if Lindy was lucky, we have to look at the preparations he made.

The main risk factor in a 33.5hr, 3,150nmi flight over ocean, is engine failure. Lindbergh selected the highly reliable Wright Whirlwind J-5C engine. Back then (1927) "highly reliable" meant a complete failure (the plane won't fly) every 200hrs (MTBF, mean time between failure = 200hr). A new car engine by comparison can be expected to run for 2000hrs or more (with oil changes etc) without failing. (A car engine doesn't have to be light, like a plane engine. A plane engine will only have just enough metal to hold it together.) Interestingly, 15yrs later in WWII, plane engines still only had the same MTBF. By then a plane (and the pilot) was expected to have been shot down by the time it had spent 200hrs in the air. It wasn't till the reliable jet engine arrived, that commercial passenger aviation became safe and cheap enough that the general populace took to it.

The failure rate of many devices follows the bathtub curve (http://en.wikipedia.org/wiki/Bathtub_curve). There is an initial high rate of failures (called infant mortality) where bad devices fail immediately. Lindbergh was flying over land, during daylight, for this part of the trip. The second phase is a low constant rate of failure, where random defects stop the device from working. At the end of life, as parts wear out, the failure rate rises steeply.

If you have a large number (n) of devices with MTBF=200hrs then on the average, you'll need to replace one every 200/n hrs.

What is the distribution of failures? While it's fine to say that if you have 200 light bulbs (globes) each with a MTBF of 200hrs, that you'll be replacing (on the average) 1/hr, what if you're in a plane and you've only got a small number (1,2 or 3) of engines? Knowing the average failure time of a large number of engines is not of much comfort, when you're over an ocean and your life depends on knowing what each engine is going to do. MTBF=200hrs could mean that the engine will have a linearly descreasing probability of functioning with time, having a 50% chance of working at 100hrs and being certain to die at close to 200hrs. Experimental measurements show that this isn't the way things fail. Presumably the manufacturer knew the distribution of failures of Lindbergh's engine, but I don't, so I'll make the assumption which is simplest to model; the flat part of the bathtub curve, with a uniform distribution of failures i.e. that in any particular hour, the engine has the same (1/200) chance of failure, or a 100-0.5=99.5% chance of still running at the end of an hour.

The Wright Whirlwind J-5C engine was fitted to single engined, two engined and three engined planes.

Let's say that Lindbergh wants to know the chances of making it to Paris in a single engine plane. Write code lindbergh_one_engine.py with the following specs

  • documentation up top, giving name of the program, author, license under which the code is distributed, and a entry describing the purpose of the code (to calculate the probability of the engine still running after n hours).
  • initialise
    		#these should be reals or you'll get integer arithmetic
    in_air=1.0	#the probability that Lindbergh is still flying
    MTBF=200.0
    time=0.0
    
  • You will update the calculation every hour. to hold the inteval between updates, initialise
    interval=1.0	#hours 
    
  • let Lindbergh fly for one interval, then update in_air. If the probability of Lindbergh still flying is in_air, what will be the probability of him still flying 1hr later [94] ? What's the probability of Lindberg still flying after interval hours [95] ?
  • At the end if the interval, print out the current time (in hours) and the probability that Lindbergh is still flying.

Here's my code [96] and here's my output

dennis: class_code# ./lindbergh_one_engine.py
./lindbergh_one_engine.py
time  1 in_air 0.99500

Note
You could have done without the variable interval and replaced it with the constant "1.0". You would have got the same result, but it's sloppy programming. A variable, which should be declard up top and which you might want to change later, will now be buried as a constant deep in the code. Someone wanting to modify the code later, will have to sort through all the constants e.g. "1.0" or "1" to figure out which ones are the interval. It's better for you to code it up correctly now and save the next person a whole lot of bother figuring out how the code works.

Now we want to find the probability of the engine still running at any time during the flight. Copy lindbergh_one_engine.py to lindbergh_one_engine_2.py.

  • Change the print statement(s), so that now you print out the header "time in-air" before doing any calculations.
  • for each time, print the value of time and in-air.
  • Lindbergh had enough fuel for 4200 miles (3650nmi) or 38 hrs of flying. Put the code which updates in_air into a loop (while/for?) and run the flight for 38hrs (store the flight time as a variable e.g.fuel_time=38).

Here's my code [97] and here's my output

dennis:class_code# ./lindbergh_one_engine_2.py 
time in_air 
   0  1.000
   1  0.995
   2  0.990
   3  0.985
   4  0.980
   5  0.975
   6  0.970
   7  0.966
   8  0.961
   9  0.956
  10  0.951
  11  0.946
  12  0.942
  13  0.937
  14  0.932
  15  0.928
  16  0.923
  17  0.918
  18  0.914
  19  0.909
  20  0.905
  21  0.900
  22  0.896
  23  0.891
  24  0.887
  25  0.882
  26  0.878
  27  0.873
  28  0.869
  29  0.865
  30  0.860
  31  0.856
  32  0.852
  33  0.848
  34  0.843
  35  0.839
  36  0.835
  37  0.831
  38  0.827

The flight to Paris took 33.5hrs. What probability did Lindbergh have of making it in a single engined plane [98] ?

Let's define luck as the probability that matters beyond your control, that will wreck your plan, will be in your favour. (In conversational terms, luck is not defined in any measurable units. It's high time that luck be put on a sound mathematical basis.) In this case Lindbergh has a 16% chance that the engine will fail. He requires 16% luck for the flight to be successful.

Figure 1. probability of engine failure stopping flight. Vertical line at 33.5hr is actual time of Lindberg's flight.


probability of engine failure stopping flight. Vertical line at 33.5hr is actual time of Lindberg's flight.

The loop parameters in a python for loop, e.g. the step parameter are integers. In the code above, step takes the default=1. We want step=interval a real. For the code above to work, we must chose an interval that is a multiple of 1.0 and we must hand code the value of step to match interval (i.e. if we want to change interval, we also have to change step in the loop code). To let maintainers know what we're doing we should at least do this

start    =0
fuel_time=2500.0
end=int(fuel_time)
interval =1.0
step=int(interval)

for time in (start, end, step):
	...

This will give sensible results as long as fuel_time, interval are multiples of 1.0 Still this code is a rocket waiting to blow up. Python while loops can use reals as parameters. Copy lindbergh_one_engine_2.py to lindbergh_one_engine_3.py and use a while loop to allow the loop parameters to be reals. Here's my code [99] (the output is unchanged). Now you can change the parameter affecting the loop, in the variable list above the loop, without touching the loop code.

about areas: The units of area are the product of the units of each dimension. The area of a rectangle, with sides measured in cm, is measured in square centimetres (cm2).

To predict the amount of energy that a power plant will need to produce for a month, the power company plots the expected temperature on the y axis, with days (date) on the x axis. The number of degrees below a certain temperature (somewhere about 17°C, 65°F) at any time multiplied by the number of days, gives the amount of heat (energy) needed in degree-days (http://en.wikipedia.org/wiki/Heating_degree_day) that customers will need. i.e. the number of degree-days is the area between the line temp=65° and the line showing the actual temperature (in winter). Tables of degree-days for US cities are at National Weather Service - Climate Prediction Center (http://www.cpc.noaa.gov/products/analysis_monitoring/cdus/degree_days/).

What's the units of the area under the one-engine graph [100] ? If you ran the graph to infinite time, what do you think the area under the graph might be [101] ? Let's find out. Copy lindbergh_one_engine_3.py to lindbergh_one_engine_4.py

  • calculate the area by cumulatively adding the probability at each measurement interval.
  • run the flight for enough hours that you get some idea of the area under the graph for an infinitely long flight.
  • print out the area under the graph at intervals of 100hrs

Here's my code [102] and here's my output

dennis: class_code# ./lindbergh_one_engine_3.py
time in_air    area
   0  1.000 1.000
 100  0.606 79.452
 200  0.367 126.975
 300  0.222 155.764
 400  0.135 173.203
 500  0.082 183.767
 600  0.049 190.167
 700  0.030 194.043
 800  0.018 196.392
 900  0.011 197.814
1000  0.007 198.676
1100  0.004 199.198
1200  0.002 199.514
1300  0.001 199.706
1400  0.001 199.822
1500  0.001 199.892
1600  0.000 199.935
1700  0.000 199.960
1800  0.000 199.976
1900  0.000 199.985
2000  0.000 199.991
2100  0.000 199.995
2200  0.000 199.997
2300  0.000 199.998
2400  0.000 199.999
2500  0.000 199.999

It looks like the area is going to be exactly the MTBF.

At what time is the area 100hr (you'll have to run the code again printing out the area at every hour)? Can you see a simple relationship between t100 and MTBF?

In the current code, the loop requires integer parameters, while the code requires real parameters. To get a reasonable estimate of the area, we have to not only intcrease time to a large number, but we have to decrease the interval to a small (i.e. non integer) value. We need to rewrite the loop using real numbers as the parameters. To do this we need to change the loop from a for to a while loop. Copy lindbergh_one_engine_3.py to lindbergh_one_engine_4.py. Make the following changes to the code

FIXME

Before we start wondering what this means, is our estimate of the area an upper bound, a lower bound, or exactly right (within the 64-bit precision of the machine) (hint: is the probability of the engine working as a function of time a continuous function or is it a set of steps? How have we modelled the probability, as a continuous function or a set of steps?) [103] ? If this an upper or lower bound, what is the other bound [104] ? So what can we say about the area under the graph [105] ?

Why are we talking about Lindbergh in a section on programming geometric series and exponential processes? The probability of Lindbergh still flying at the end of each hour is a geometric series

p       = r^0, r^1, r^2 ... r^n
where r = 0.995

Using the formula for the sum of a geometric series, what's the sum of this series

Sum     = r^0 + r^1 + r^2 ... r^n
	= 1/(1-0.995) = 200

?

Why is the area under the graph = MTBF = 200hrs?

The next problem is fuel - if you run into headwinds or have to go around storms, you'll run out of fuel. A two engined plane can carry a bigger load (more fuel). However one engine alone is not powerful enough to fly the heavier plane. If you have two engines each with a MTBF of 200hrs, what's the MTBF for (any) one engine? It's 100hrs. (If you have 200 light bulbs, each with a MTBF of 200hrs, then on the average there will be a bulb failure every hour.) Copy lindbergh_one_engine_2.py to lindbergh_two_engines.py. Change the documenation and the MTBF to 100hrs and rerun the flight for the same 38hrs (Lindbergh will now have more fuel than this). Here's my code [106] and here's my result

./lindbergh_two_engines.py
time in_air 
   0  1.000
   1  0.990
   2  0.980
   3  0.970
   4  0.961
   5  0.951
   6  0.941
   7  0.932
   8  0.923
   9  0.914
  10  0.904
  11  0.895
  12  0.886
  13  0.878
  14  0.869
  15  0.860
  16  0.851
  17  0.843
  18  0.835
  19  0.826
  20  0.818
  21  0.810
  22  0.802
  23  0.794
  24  0.786
  25  0.778
  26  0.770
  27  0.762
  28  0.755
  29  0.747
  30  0.740
  31  0.732
  32  0.725
  33  0.718
  34  0.711
  35  0.703
  36  0.696
  37  0.689
  38  0.683

Lindbergh won't have to worry about running out of fuel anymore. Now how lucky does Lindy have to be [107] ? Would you rely on this amount of luck for your life?

Before we go on to the 3 engine case, we didn't derive the formula for the one or two engine case rigourously, and the method we used doesn't work for 3 engines. So let's derive the formula in the proper way. For this we're going to make a side trip to learn some elementary_probability.

One engine case chance of probability of cumulative probabilty total engine failure engine running of single engine failure probability in that hour at end of hour by end of that hour 1hr 0.005 1.0 *(1-0.005)=0.995 1-0.995=0.005 0.995+0.005=1.0 2hr 0.005 0.995*(1-0.005)=0.990 1-0.990=0.01 0.990+0.010=1.0 . . MTBF=200hr Note: at any time (probability of the engine running) + (probability of engine not running) = 1.0 Two engine case: calculate probabilities where plane can only fly on 2, 1 engines chance of chance of either chance of only chance of both chance of no engine failing engine failing one engine running engines failing engines running in that hour in that hour at end of hour in that hour at end of hour engine 1 engine 2 1hr 0.005 0.005 0.005+0.005 = 0.01 1.0 *(1-0.01)=0.99 0.005*0.005=0.000025 1.0 *(1-0.000025)=0.999975 2hr 0.005 0.005 0.005+0.005 = 0.01 0.99 *(1-0.01)=0.98 0.005*0.005=0.000025 0.999975*(1-0.000025)=0.999950 . . MTBF engine 1 engine 2 either engine both engines 200 200 100 40000 Three engine case: calculate probabilities when plane can only fly on 3, 2 and 1 engines.

The next possibility is a 3 engine plane. This could carry the extra fuel and it could still fly with on two engines.

Lindbergh thought not. As Shackleton said on his first attempt at the south pole, when he turned back 88 miles before the pole "better a live donkey than a dead lion".

1.19. Getting in early when you have an exponential process

Building a house (or a rocket) is a linear process. You start with the basement, then the floors, then the walls, the roof... You can't build the roof any faster if the basement is finished in half the time.

Any process where later stages are helped if earlier stages are better will be an exponential process. Examples (other than investing early) are

  • aquiring skills (mental or physical): Mastery of skills will advance you to learning the next set of skills. You will move to a new cohort of people with these skills, who will show you more skills and will challenge you to improve your skills.
  • acquiring knowledge. This is hard at first. Look how long it takes to learn to walk and talk. When you know little about a subject, you have no framework to place the new knowledge in: you can't test it, you don't know its relevance, you'll forget it or have no reason to remember it. As you accumulate knowledge, you will find that new pieces of information can be placed into your current framework; you can test whether it's true (and if not discard it and remember why it was wrong). Soon you'll know enough to see voids in your knowledge and look for ways to fill them in.
  • computer programming. Writing a computer program can take many man-years; much longer than the time it would take to do the calculation by hand (once). The payoff only comes if big program can be run hundreds, thousands or millions of times. You only write programs if they will be used many times. Computer programs lead to better computer programs: the weather forecasting code in use today is the descendant of weather modelling code first written in the 1950's. The same can be said of compilers and many other classes of computer applications and tools.
  • first to market. If you're the first to market with a device, you can corner the market and have it to yourself, even if your product is junk.
    • the IBMPC, which lead to the current desktop and laptop computers. The IBMPC used the 8088 chip as the CPU. The 8088 was a dreadful design and greatly limited what you could do with your PC. It was only chosen because IBM didn't expect to sell many PCs, and they already had an 8088 based design, which would save development costs. However with the PC backed by IBM, and the design made available to any PC manufacturer, the PC swept the marketplace. This took Intel, the manufacturer of the 8088, from obscurity to the place of the dominant CPU maker in the world, with the manufacturers of the well designed CPUs having gone out of the market. Computers based on the descendants of the 8088 became the commodity CPU and earned enough money for Intel, that Intel took over the role played by high end Unix servers which ran on well designed and expensive CPUs. The process of junk forcing out good quality products from the market place is well understood by computer professionals, much to their sorrow, and not thought worthy of further comment. However if you're an economist and you write about it in academic journals, you'll get a Nobel Prize (see Akerlof http://nobelprize.org/nobel_prizes/economics/laureates/2001/).
    • Windows (particularly W95. Aided by good marketing by Microsoft and poor marketing by IBM, Win95 (and its descendants) became the dominant OS for PCs, beating out the better OS/2 by IBM.
    • DOS. DOS was the OS of choice for the early PCs. (It wasn't an OS, it was a program loader, each application had handle the OS tasks itself. As a result only one program could run at a time.) It sold for $25, while the competitor CPM-86 sold for (I think) about $150. A functional version of Unix (called XENIX) was available for the PC, but was not adopted by the masses. Many people copied DOS and used their free copy. Microsoft sold truck loads of DOS, and the people who didn't pay for DOS, now had to use the programs which depended on DOS (most of which were written by Microsoft) so Microsoft laughed all the way to the bank. Software vendors had thin margins and only wrote applications for DOS, cutting CPM-86 and XENIX out of the market. DOS was a loss leader for Microsoft, allowing Microsoft to dominate the PC marketplace from the beginning.

After you've built your first rocket, or house, you will be in a better position to build a second house, or dozens of houses (rockets) just like the first one.

The point is: if it's an exponential process, get in early and stick with it.

It seems that to excel in a field (whether playing music or computer programming), you need to first spend about 10,000hrs at it A gift or hard graft? (http://www.guardian.co.uk/books/2008/nov/15/malcolm-gladwell-outliers-extract). No one seems to be able to differentiate genius from 10,000hrs of hard work.

1.20. Presentation: e, exponential and geometric series

Give a presentation on exponential processes (you can use any content from this webpage).

  • Show how bank interest, earned over a fixed time interval, changes when the period for estimation changes (period=year, month, day, hour, second, very small interval). State that this method allows you to calculate e. Show that this method for calculating e is not practical by doing the following:
    • Give estimates for the time that it would take to calculate e to the precision of a 64-bit real
    • give the worst case estimate of the errors expected
    • Compare your best estimate for e using this method, with the actual value of e
  • Show the factorial series for calculating e. Discuss its speed and worst case errors. Show the role of arithmetic progressions in determining the errors.
  • Show the effect of compound interest on savings over a long period of time. Discuss the financial consequences of choosing a career as a plumber or a Ph.D.
  • Show the effect of interest in buying a house with a mortgage.
  • Discuss the effect of Moore's Law on calculating e=(1+1/n)n. Discuss Optimal Slacking and the futility of interstellar space travel.
  • Calculate the volume of the pile of rice that was given as a reward to the inventor of chess. Show how long it would take to deliver the rice if a linear process was used, or if an an exponential process was used.
  • Was Lindy lucky? Give an estimate for how lucky he was.

2. Nim (the subtraction game)

Nim (http://en.wikipedia.org/wiki/Nim), and the subtraction game (http://en.wikipedia.org/wiki/Nim#Other_variations_of_the_game). The real Nim game is difficult: you remove counters from multiple lines of counters. We're going to code up the simplest version, which has only one line of counters, and is better called "the subtraction game".

Nim was an early computer game shown to the public, running on the hardwired Ferranti Nimrod computer (http://jwgibbs.cchem.berkeley.edu/nimrod/) back in 1951. (Nimrod means "hunter" and was the name of a Mesopotamian King mentioned in the Bible. Nowadays computers are reprogrammed by software. Back in 1951, the code was hardwired into the computer by plugboards.)

2.1. Nim: Rules

In the simple version of the game (better called the subtraction game) you start with a variable number of counters (stones, coins, matches, paper clips); two players take turns to pick up 1..n counters (where n is given by the computer or agreed upon by the players). The person who picks up the last counter(s) is the winner (or in the misere - inverse - game, the winner).

2.2. Nim: Strategery

The winning strategy is simple: see Jim Loy's Nim page (http://www.jimloy.com/puzz/nim.htm). Let's run the game backwards from your win and assume that the maximum number of counters a player can pick is 5 (i.e. players have to pick up 1..5 counters).

  1. your play:

    counters=1..5 - you pick them all up and win (We're only exploring games where you win. If there were any other number of counters, you aren't guaranteed of a win.)

  2. opponent's play:

    • 6 counters: opponent has to pick 1..5, leaving 5..1 counters - you win.
    • 7..11 counters: opponent can leave 2..10 counters.
      • If opponent leaves 2..5 counters, you pick them all up - you win.
      • If opponent leaves 6 counters, you can only pick 1..5 leaving 5..1 counters - you loose
      • If opponent leaves 7..10 counters, you can leave 6 counters - you win
    • 12 counters: opponent leaves 11..7 counters, you can leave 6 counters - you win.

Strategy for a game in which you can pick 1..n counters: if you leave your oponent with a multiple of m(n+1) counters, you win. Otherwise you can loose (unless your oponent makes a mistake).

What is the opponents's optimum strategy? It's the same as yours: to leave m(n+1) counters on the table.

What's the best move if you can't leave m(n+1) counters on the table (i.e. your opponent has left m(n+1) counters on the table, whether by luck or by knowing the winning strategy)?

  • If your opponent knows the strategy and doesn't make mistakes, you're doomed: so a random number is as good as anything.
  • If your opponent can make a mistake, then probably all you can do is to let them make as many mistakes as possible, so pick up a small number of counters, hoping that they also pick up a small number of counters, leaving you with m(n+1) counters.

    The simple strategy then would be to pick up only one counter. A slightly better strategy, incase the opponent recognises that you only pick up 1 counter when you're in trouble, would be to pick up a random number of counters between 1..max_num/2.

(This isn't a mathematical proof: it's just my idea of what to do.)

What should you (and the computer) do in a game where you're allowed to pick up 1..5 counters and there are 24..29 counters on the table [108] ?

What will happen if the computer lays out 60 counters and you're allowed to pick up 1..9 counters [109] ?

Have students play games till the first player can win every time.

Now you know enough to play Nim. It will take several more sections of this document and about 100 lines of code to get a computer to do the same thing. As you progress through the coding, note that processes taken for granted by humans, have to be made explicit for the computer. A human on reaching a branch point will evaluate all the choices, realise that most of them are pointless (I shouldn't play if there are 0 counters), and see the right thing to do. The computer has no global view of the game and only knows about manipulating bytes. You, the programmer, have to give the computer the rules of the game, including the really obvious ones.

2.3. Nim: a working but fragile version

Note

You always code in steps, and at each step, the code must work. This is called "evolutionary coding". The name derives from Darwinian Evolution, where to evolve from one organism to another, all intermediate organisms must be viable. A change (mutation) that results in a dead animal is not going to leave any descendants. Similarly your code must work after any change. The people who are paying you to code might come in one day and ask how it's going. If you can show them screens doing this and doing that, and one of them notices a screen that isn't doing anything, you can say "ah yes, I haven't written the code for that yet", and they'll go away happy. They won't be impressed if you tell them you have the best program on earth, but it doesn't run yet [110] .

The other method of coding is called "intellegent design", where you design all the code from scratch, then code it all up, but have nothing that works for months, and then wonder why the final code doesn't do what you expected.

Write code (nim.py) in the following steps. Make sure your program runs at each step before you add the next step.

  • documentation

    write the documentation section at the top (delineated by two sets of triple quotes), with the name of the program, author name, date (year), license, and a brief description of the program. Use one of your previous pieces of code as a template if you like.

  • write dummy main()

    write main() that starts with

    #----------
    #main()
    

    and ends with

    #-nim.py----------
    

    to show the end of the file. (There is no code in main() yet.)

    As you write your code, separate logically separate parts with #----- and with comments in this way. Another person reading the code, shouldn't have to look at the actual code to figure out what's being done in that section.

    #-------
    #greeting
    
    (code)
    
    #------
    #calculate parameters for game
    
    (code)
    
    #------
    #player's turn
    
    (code)
    
    
  • write welcome()

    write a function welcome() to annouce the name of the program (Nim) and give the rules.

    When you start writing a function your first two lines will be the start and the end

    def my_function():
    #--my_function()-----
    

    In general, whenever you write blocks with a defined start and end (e.g. a function, a loop), you write the start and end lines first, and then fill in the middle.

    Document the function of welcome() using the documentation template as a guide.

    As for the functionality of welcome(), for the momemt make welcome() give the name of the program "Nim" and then just say "here are the rules" followed by a blank line. You'll return to fill in the rules later. This is called "writing a stub" - it looks OK to the rest of the program and allows the rest of the program to run, but is not really doing anything yet.

    The usual reason to make code a function, is if it's used multiple times. welcome() will only be run once, but you can make a function out of a self contained logical block. When reading through main(), it's not hard to guess what welcome() does.

    What parameters are passed to and returned from welcome() [111] ?

    Leave a blank between the call to welcome() and the next logical block, to make reading the code in main() easier. Separate logically distinct blocks by an empty line (in the same way as paragraphs are used in text).

  • generating random numbers

    You now want to generate two numbers: the number of counters and the maximum number (max_num) of counters that the players can pick up each turn. I initially calculated these as two separate numbers. A little later, I realised that the length of the game (i.e. the number of turns each player gets) depends on the ratio counters/max_num. I decided it would be best to have about the same number of turns for each game and that about 6 turns was a good number. (You would need to do customer testing to see if this assumption is true.) When you write programs, you often find a better way of doing it after thinking a little. This is quite normal. Rather than let you figure this out yourself, I'm giving you code with the number of turns approximately constant.

    Since you want the game to be different each time, you use a random number generator to produce counters, max_num. All computer languages have instructions to generate random numbers (see 6.4 random http://docs.python.org/lib/module-random.html). To see some code useful for this section see random numbers.

  • generate the maximum number of counters that can be picked up in any turn

    In main() use randint() to generate the maximum number of counters a player can pick up. Store the result in a variable called max_num. Make max_num have a value in the range 5..9.

    Your first attempt might look like this

    max_num = random.randint(5,9)
    

    Having the numbers 5 and 9 hardwired into randint() makes difficult any maintenance or later changing of the code. The programmer will have to go through the whole program looking for them. The programmer won't know if a "5" is the lower limit for max_num or some other constant. Instead have a section like this, containing user settable constants (usually called "user defined variables"), below any import statements and before any code. (Include the string "#user defined variables", so the maintainer can locate this part of the code.) For more info on user defined variables see user defined variables.

    #user defined variables
    
    lower_limit_max_num = 5	#max_num is the maximum number of counters a player can pick up
    upper_limit_max_num = 9
    

    These names are hopefully self documenting. The randint() code now becomes

    max_num = random.randint(lower_limit_max_num, upper_limit_max_num)
    

    If you (or years later, someone else) want to change the dynamics of the game, you look for the section labelled #user defined variables and start editing there. You don't have to start spelunking the code (with the risk of messing it up with a slip of the fingers).

    The algorithm depends on the minimum number of counters you can pick up being 1. You need to declare min_num=1 or you'll have the constant "1" spread throughout your program. Only have a "1" in the program if you really need the number "1", when it's required by the laws of physics (or math), and not a number that can be varied (even if only in principle), like you do when figuring out the number of fenceposts for a 10 section fence. Put a comment describing the role of min_num and why you shouldn't change it.

    You're still in main(). Output to the screen a message saying "you're allowed to pick up min_num..max_num counters" (outputting the values of min_num, max_num).

  • generate the number of counters to start the game

    In main() use randint() to generate the number of counters for this game, and store the result in a variable called counters.

    You need to decide the range of values allowed for counters. With the length of the game set to about 6 plays, you'll need (max_num*2*6)=60..108 counters. In a later section we're going to output the counters across the screen. The usual terminal width is 80chars, limiting the maximum number of counters to 80. (Make sure you comment the reason for choosing 80char, so a maintainer won't think you pulled the number out of thin air.) Since the length of the game is dependant on max_num, I'd make the minimum allowed value for counters to be some multiple of max_num. I'd say the multiple should be 3 or more (I picked 6, allowing 6 turns on average). You pick a value you like for the number of turns.

    The code then will look something like

    #user defined variables
    upper_limit_counters = 80	#the width of the terminal
    minumum_number_plays = 3
    .
    .
    .
    #main()
    counters = random.randint(minumum_number_plays * max_num, upper_limit_counters)
    

    In main() output the number of counters to the screen using a string like "the number of counters is ".

  • Construct code for the player's turn

    The player goes first. Output a message from main() saying something like "Your turn: There are n counters. How many counters do you want to pick up?" and store the answer in resp (response) using this code fragment as a template.

    >>> resp = raw_input ("What is your name?")
    What is your name? Kirby
    >>> print resp
    Kirby
    

    Make sure there is a blank between the end of the prompt string and the place where the user enters their number. To output the number of counters in the prompt string, assume that raw_input() calls the function print() to output the prompt string.

    As we saw from the parameters for randint(), we struck out making an assumption like this, since randint() didn't use range() to handle the parameters, even though the syntax was the same. However here the assumption works. If you strike out, you'll get the wrong answer or the interpreter will complain, in which case you shrug your shoulders and go to the documentation for raw_input().

    For the moment we're going to rely on the player (you) entering valid numbers. Before you unleash your code on unwitting real people, you must gracefully handle out of bounds responses from players (tell them what they did wrong and let them try again). We'll handle this later, but before we merrily go on, record what can go wrong in comments, so that later when you return to handle it, you won't have to scratch your head to try to remember what you were thinking at the time you wrote the code. If you forget to handle it, you'll eventually find your comments and fix the code.

    What invalid input can the player enter?

    • a string e.g. "5d", "Homer Simpson".
    • a real e.g. 2.71828
    • an out of range integer, i.e. an integer which is not min_num..max_num

    Record these conditions, as conditions which must be handled, in comments in your code.

    Output the player's input to the screen with a string like "you're picking up n counters".

    What primitive data type is resp? What primitive data type do you need for this program [112] ? If you need to change the data type, recall how you changed a string into a float (see operations on strings). Guess the instruction to change the datatype to what you want. Store the result in players_resp. Comment out the previous line outputing resp and replace it with a line outputting players_resp.

    Calculate the new value of counters. Change the output line "you're picking up n counters" to something like "you picked up n counters, there are m counters left". Here's a code fragment showing how to output multiple numbers.

    >>> first_number=1
    >>> second_number=2
    >>> print "%d %d" % (first_number, second_number)
    1 2
    

    Here's my code for the player's turn [113] .

  • computer's turn

    It's the computer's turn. What is the algorithm that the computer should use [114] ?

    write a function computers_turn() that takes the value of counters as the first parameter, max_num as the second parameter and returns the result of its play. Inside the function use another name for the values passed to it (i.e. don't use counter: if you don't have a better name, variables local to a function start with "my_" e.g. "my_counter" or the name of the function e.g. "computers_turn_counter").

    Document the algorithm used (or will use when you write it), the type of parameters it accepts and the type of parameter it returns.

    For the moment have computers_turn return 1 (i.e. the computer picks up 1 counter). This code is called a stub (the code has to work - it doesn't have to be correct - at least yet). In main() output the result of the computer's play, then print a blank line, and then tell the player it's their turn again.

    Here's my computers_turn() so far [115] and here's my output [116] .

    Now we code up a better play for the computer. This code fragment uses the modulo (%) operator to return the remainder after division.

    >>> number=52 
    >>> divisor=7
    >>> number%divisor
    3
    

    How do we use this code to test if there's a winning move for the computer [117] ? What remainder will show that we're doomed [118] ?

    Change computers_turn() to use the modulo operator to determine the remainder after dividing by (max_num+1).

    The code will have to branch according to whether the computer is in a winning position or is doomed. Think what the computer has to do with the remainder in each of these situations. Use this code fragment as a template for the branch described below.

    >>> number = 99
    >>> result = number
    >>> if (number != 99):
    ...     result = 44
    ... 
    >>> print result
    99
    

    Test if the computer is allowed to pick up this number of counters.

    • If yes, return this number.
    • If no, then the computer is doomed. Do a conditional branch. In the conditional branch, insert a print statement to show that you've branched. (This is for debugging only. You'll remove it or comment it out, when you're happy that your code is working.)

      In the conditional branch, find a number to for the doomed computer to pickup (see Nim strategy). Initially you can use the simple strategy; once you have it working, comment it out and try to write the slightly more complicated strategy.

      For debugging, add an else: branch, containing only a print statement to show that the computer played a winning move. (You'll comment out or remove this when you've checked that the code is correct.)

      Note

      Problems with max_num

      • If max_num/2 is not an integer, randint(min_num,max_num/2) will fail. This could possibly happen if max_num is odd. How can you handle the possibility of not having an integer for the second parameter [119] ?

      • If the second argument to randint(1,int) is 1, then randint() will succeed and give a sensible result. If however the second argument is 0 (or less), then randint() will fail.

        >>> import random
        >>> print random.randint(1,1)
        1
        >>> print random.randint(1,0)
        Traceback (most recent call last):
          File "<stdin>", line 1, in ?
          File "/usr/lib/python2.4/random.py", line 216, in randint
            return self.randrange(a, b+1)
          File "/usr/lib/python2.4/random.py", line 192, in randrange
            raise ValueError, "empty range for randrange() (%d,%d, %d)" % (istart, istop, width)
        ValueError: empty range for randrange() (1,1, 0)
        >>> 
        

        Under what conditions will the 2nd argument to randint() be =<0 [120] ? The code at the moment is

        #user defined variables
        lower_limit_max_num = 5 #max_num is the maximum number of counters a player can pick up
        upper_limit_max_num = 9
        

        You're writing the code and you know how nim works, and you know that you won't get a very interesting game if max_num==1 (why not [121] ?). Someone else who starts playing with (or maintaining) the code may not know how the code works or how nim is played. They might think that 0,1 are perfectly sensible minimum numbers, particularly since you didn't leave any warnings, and they might reset lower_limit_max_num in #user defined variables.

        If this code were running in a rocket, or a heart monitor, you would need to include warnings in #user defined variables AND put code to detect out of range values and return them to some sensible values. Instead just add warnings.

        Here's my new #user defined variables section

        #user defined variables
        #
        #max_num is the maximum number of counters a player can pick up
        #
        #lower_limit_max_num must be 2 or greater for a sensible game.
        #The program will crash at runtime if lower_limit_max_num is =<1
        #
        lower_limit_max_num = 5
        upper_limit_max_num = 9
        
        upper_limit_counters = 80       #the width of the terminal
        minimum_number_plays = 3
        

        A lot of writing code is thinking about seeing out of bounds conditions. Humans have had a lot of practice handling these conditions and don't think about them a whole lot. However when you have to explain them in a new context, like writing code, you have to make them all explicite. When people started driving cars, they'd just come from riding horses. The car salespeople had to deal with people learning about cars. A horse would never run head first into a phone pole if you looked around, or go into a ditch and then roll over, yet cars did it all the time, much to the aggravation of their new owners, who wondered why they'd spent their good money on this new fangled car, when their horse would never do anything so stupid.

        You're in the situation of the people adapting to cars. The computer has no idea that the game will crash for the wrong values of max_num. All the computer knows is whether a bit is 0/1. Don't expect a lot of help from the computer when coding a game like nim.

    Here's my code in computers_turn() [122] .

    Run this code to show one pair of turns (player, computer).

    Note
    You now have code that does all the essential steps of Nim. Watch to see how much more code you need to write to get a usable game.
  • looping

    You're about to do surgery on your code. You risk messing it up and not being able to recover a working version of code. (There are code developement packages like CVS which store all your old versions of code, allowing you to recover mistakes. These only work if you have access to the CVS server. If you develope whereever you happen to be and aren't in contact with the server, these tools aren't much help.) Save your current file to nim.py.v1. Hopefully you'll never need to look at your old file again, but it's there if you need it.

    Since the computer and the player take turns till the game is over, you code their plays into a while loop (here in pseudo code).

    while (game not finished)
    	player's turn
    	computer's turn
    
    #we get here only when the game is over
    declare winner
    

    What's the test for the game being over [123] ?

    There's a logical problem with this pseudo code. Will the while loop exit correctly no matter which player wins [124] ?

    The while loop will exit correctly if the computer wins. We have to stop the computer from playing if the player wins. There are two logically equivalent ways of handling this.

    • After the player's turn, test if the game is over. If the game is still running, branch to allow the computer to have a turn, else do nothing.
    • Allow the computer to have a turn no matter what, but don't let the computer pick up any counters if (counters==0).

    With both of these, the while loop will now exit if the player wins as well. Which one requires modification of computers_turn(), which one requires modification of code that calls computers_turn() [125] ?

    With these choices being logically equivalent, which do we choose and why?

    • modify calling code:

      The design of the while is such that we don't have code to stop the player from playing if (counters==0), since the while loop will have already exited and not give the player a turn. It would be consistent if we didn't put any code to detect (counters==0) in computers_turn(). This would mean that we should modify the calling code.

      In the game between humans, if the first player makes the winning move, he/she will jump up and down claiming victory and the second player will not get a chance to play.

      The pseudo code in this case is

      while (game not finished)
      	player's turn
      	if (game not finished)
      		computers_turn()
      	else 
      		do nothing
      
      #the game is over, so someone has won
      declare winner
      
    • In the game between humans, if the first player wins, they wait till the second player looks at the number of counters and acknowledges that they can't play. The pseudo code then is

      while (game not finished)
      	player's turn
      	computers_turn()
      		#inside computers_turn()
      		if (counters==0):
      			return 0
      		else 
      			do normal play
      
      #the game is over, so someone has won
      declare winner
      

    It would seem we don't get any help there. It seems that logically equivalent choices are, well, equivalent. The only difference is where you put the line if (counters==0).

    We've got one more thing to get from the while loop: the winner. Let's see if this affects our design. In the two previous pieces of pseudo code, after each line, say what we know about the winner [126] ?

    In the 2nd version only the computer knows it has won and has to pass the results back to the calling code. Functions can only return one value and computers_turn() is already passing back the number of counters it picked up. The function computers_turn() then would have to modify a variable in the calling scope. Having modifiable global variables is an unsafe coding practice, as it's hard to keep track of the code that can modify a variable. Depending where you work and what you're working on, you may not be allowed to use global variables.

    In that case, we should use the first version. Possibly by luck it's shorter and easier to read. The idea is when branching, to keep information at the same level (in this case, associated with the while loop). If the calling code can tell that the player has won, then the calling code should also know that the computer has won.

    Here's the pseudo code for the while loop

    winner="computer"	#always initialise variables
    while (game still running)
    	player's turn
    	if (game still running)
    		computers_turn()
    	else
    		winner="player"
    
    print "the " + winner + " has won!"
    

    Note the logic, which sets the value of winner. As discussed in the while loop, this is a variable that accumulates state as the while loop is executed.

    The while loop will set winner to "player" if player wins, otherwise it doesn't change the value of winner. On exiting the while loop, one or other of the players has won. You therefore initialise winner with the other value.

    while loops often set variables to one of two values. Before you enter the while loop, you initialise the variable to the other value.

    Code this up for your version of nim.py, and fill in the rules in welcome(). Here's my code [127] .

2.4. Nim: Handling User Input

The user isn't always going to enter valid numbers.

  • What if the user accidentally enters "5d" for the number of counters they want to pick up? The program will crash exiting the game (and just when you were about to beat the computer for the first time too). See if you can figure which line of code will cause the crash with input "5d".. Once you've located it, try entering "5d" (or any non-number) and watch the back trace from python. Here's the line that will crash [128]
  • What if the user wants to remove more counters than are left, 0 counters, a negative number of counters, more counters than they're allowed?

This leads to the concept of valid input. The only valid inputs are min_num..max_num. Any other inputs must be trapped, without changing the state of the game, and the user allowed to try again. A tester should be able to pound on the keyboard in any random fashion without the program crashing and the tester should be presented again with the string requesting input. Any other behaviour and the code is called fragile code.

Handling errors is not easy in any language. The modern languages (like python) handle errors better than the old languages. I'm not going to take you through error handling here, however here's a bit of code that asks for user input and will only exit when a valid integer is entered.. Swipe it with your mouse, enter some valid input, invalid input, strings, reals and see what it does even if you aren't familiar with the syntax.

#!/usr/bin/python

#modelled on code from http://www.wellho.net/mouth/1236_Trying-things-in-Python.html

upper = 9
lower = 5
val = 0 #prime the while loop to run by initialising val to an invalid value

while ((val > upper) or (val < lower)):

	#uval is the user entered value
	#it will be the string representation of whatever the user entered,
	#whether it was originally an integer, float or string.

	uval = raw_input("input an integer in the range %d..%d: " % (lower, upper))
	try:
	        #is uval the string representation of an integer?
	        val = int(uval)
	except:
	        #no, it's a string or the string representation of a real. Give the user feedback.
	        print uval + " is not an integer - try again."
	else:
	#yes, val is an integer
	#if invalid value, then help user
	if ((val > upper) or (val < lower)):
	                print "error: val %d is outside range %d..%d" % (val, lower, upper)
	#else valid value, exit while loop

print val

You're about to do another round of surgery on your code. Save your current (working) version of nim.py as the next saved version num.py.v2.

Then use the above code to make a function players_turn(l,u) passing as parameters the lower and upper limits of the number of counters the player can pick up and returning the number of counters the player picked up.

Here's my function players_turn() [129] .

The function players_turn() doesn't know the value of counters. Part of the prompt for the user will have to come from the calling code. A ',' at the end of a print statement supresses the carriage return e.g.

>>> number = 1
>>> print "%d" % number,
1

What about the 2nd parameter? Should it be max_num? What if this results in leaving a negative number of counters? If we had such a play, was the previous play (by the computer) a winning strategy [130] ? Do we need to worry about this case [131] ? Handle this case in the calling code.

If the (counters < max_num) we can't allow the a player to pick up max_num counters. We can only allow the player to pick up counters counters. Here's code that will do this

	upper_limit=max_num	#upper_limit is only used in these lines as a temporary variable
	if (counters < upper_limit):
		upper_limit = counters
	players_resp = players_turn(min_num,upper_limit)

Python has a built-in function min(param1,param2...paramn) which returns the smallest parameter (all languages have a min() although most only take 2 arguments).

The replacement code using the built-in python function min() is

	players_resp = players_turn(min_num,min(counters, max_num))

Using min() we get this code.

	players_resp = players_turn(min_num,min(counters,max_num))

The previous conditional was executed only if the number of counters on the table was less than the number of counters you were allowed to pick up. In the proposed replacement using min(), the function min() will be executed no matter whether counters is smaller or greater than max_num. Will we still get the results we want (try it with max_num==10 with counters==5 and then counters==15) [132] ? Use the appropriate code to calculate the values to pass to players_turn().

You're making a nested call to a function (one function calls another function). It makes the code a little harder to read but min() is a well known function, so you should be able to handle this level of complexity.

Here's the change in the calling code in main() [133] .

players_turn() contains a nested call to min(). Why is this call to min() in the Nim code OK, but a nested call to compare_results() (see train wreck) is not? It's a judgement call: min() is a function available in all languages - a programmer will recognise the function, and the nesting level isn't terribly deep. You've eliminated 3 lines of conditional code, that you'd have to think about a bit to figure out what it was doing. You could have used a train wreck style call in both cases and no-one would have minded too much.

2.5. Nim: better user interface: storing the game history

Writing code is easy for coders. Figuring out an interface that users will enjoy using is beyond most coders. There is often more code in the user interface than in the real guts of the program. At the stage in the program that you've reached, most coders will say "who cares about the users?" and walk away. However you won't get any acclaim or money if no-one uses your code, even if it's correct and does the job perfectly.

While code that doesn't work, or is badly written comes from the first circle of Hell of the programming world, code that works perfectly that no-one can or will use due to lack of documentation or a bad user interface is from the 2nd circle (see Divine Comedy http://en.wikipedia.org/wiki/The_Divine_Comedy).

In the game so far, let's say we've had the following plays

		play	counters remaining
start 			59
player		3	56
computer	5	51
player		4	47
computer	4	43	

Currently the player sees this dialogue with the history of the game scrolling off the top of the screen.

Your turn. There are 51 counters. You can pick 1..9 counters: 4
you're picking up 4 counters. There are 47 left.
the computer picked up 4 counters. There are 43 left.

Your turn. There are 43 counters. You can pick 1..9 counters: 

It would be better if the player could see the game history at each play. Here's a proposed replacement for the last line

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXccccppppcccccppp
    '    1    '    2    '    3    '    4  ^ '    5    '    
Your turn. There are 43 counters. You can pick 1..9 counters: 

The dialogue above this new prompt would still scroll off the top of the screen, but now the player would be given a fresh summary of the game at each play. Hopefully the points conveyed in this new prompt are

  • There are 43 counters currently on the table
  • The game started with 59 counters
  • The past plays (player - p, computer - c) are shown in order (3,5,4,4 counters respectively).

To produce this prompt, we need code to hold the history of the game (i.e. the counters that the player/compute pick up at each turn).

Before doing anything with lists, make sure you understand the basic information about programming with lists.

We'll use a list called plays[] which will hold the plays (int). Here's how we could use append() and a few other commands to store and retreive the game's history (for the moment, we aren't keeping track of the number of counters remaining in play).

#initialise plays[] as an empty list
>>> plays=[]
#add entries to list
>>> plays.append(3)
>>> plays.append(5)
>>> plays.append(4)
>>> plays.append(4)
#write out list (as a list)
>>> plays
[3, 5, 4, 4]
#how many entries are there?
>>> len(plays)
4
#write out the list as individual items
>>> for play in plays: 
...     print play
... 
3
5
4
4
>>> 

In object oriented programming (OOP), the instruction for doing something to an object is object.do_something(), e.g. plays.append(3). You would expect that the instruction returning the number of entries in plays[] to be plays.length(). However python (and a few other languages) use the syntax len(plays). Since length() is ambiguous (it could be the number of items, the amount of memory required to hold the data, or the total length of the strings, rather than number of strings) a better instruction, to show the number of items in a list, would be plays.count(). Understanding why len(plays) is used instead of plays.count() is a part of understanding objects. If you really, really, really understood objects you would know (it appears to be a holdover from the early days of OOP, that should have been deprecated a long time ago). For the rest of us (including me), it would appear that there still aren't enough rockets blowing up.

We want a function to update the list plays[] - call it nim_prompt(). First we need to declare plays[] and to initialise it (to the empty list) with

plays=[]

Where should plays[] be declared? If we declare/initialise plays[] inside our function nim_prompt(), then each time we call nim_prompt(), plays[] will be reinitialised (i.e. set to empty). This will defeat the purpose of plays[], which is to hold information about the state of the game (otherwise called state information).

Note
What is state? State (http://en.wikipedia.org/wiki/State_(computer_science)) - information about the configuration of the system. (I know this a somewhat circular definition.)

Non-object oriented languages (e.g. C), allow you to declare a local variable to be static (see Static variables http://en.wikipedia.org/wiki/Static_variable). These variables maintain their value between calls to the function (i.e. the value of the variable is persistent), allowing the function to maintain state.

In some object oriented languages, such as Python, there is no keyword or concept of static. The proper python way of maintaining state through calls, is to create an object (say nim_prompt) which has plays[] stored inside). Since objects persist throughout the existance of the program, the content of plays[] will not be reinitialised whenever a call is made to update nim_prompt().

We don't have static and we aren't doing OOP. What's left?

The oldest way of maintaining state, is to make plays[] a global variable (i.e. to declare it in global namespace), where it will only be initialised once. Being in global namespace, plays[] can be seen (and altered) by all functions. Because of the possibility that you might blunder and write code that changes the global variable in a way you didn't realise, or because someone in the future might add a function that changes a global variable, using global variables is regarded as a poor programming practice. But in the absence of better alternatives, it's the one we're going to use.

We will write a function nim_prompt() to generate the prompt after each play. Since this is a function, we can write and test it independantly of the Nim game code we've written so far. We'll need a dummy main() which feeds the neccessary info to nim_prompt() (here, that the game started with 59 counters and the subsequent 4 plays have been 3,5,4,4). When we're done, we'll move our function nim_prompt() into our game code, and add the appropriate calls to main() in our Nim code. Here's the file we'll start with (call it test_nim_prompt.py). main() has a section for global variables, which is after user defined variables (if you have them) and before main().

#!/usr/bin/python

#functions

def nim_prompt(play, counters):
	#parameters
	#play: number of counters picked up for this play
	#counters: number of counters left after play

	plays.append(play)
	print plays

# nim_prompt-----------------------

#user defined variables (if you have them)

#global variables
plays=[]

#-------------------

#main()

#In the real code these calls will come from a loop in main().
nim_prompt(3,56)
nim_prompt(5,51)
nim_prompt(4,47)
nim_prompt(4,43)

# test_nim_prompt---------------------------------

This version of nim_prompt() has little functionality beyond accepting its parameters. The function at this stage is called a stub. It allows main() to go about its business as if nim_prompt() was fully functional. We can then write code for nim_prompt() when we're ready and without disturbing main(). Another person can continue working on the original Nim code, while you work on nim_prompt().

Here's what happens when we run this code interactively under python. The first four lines of output are from the nim_prompt() instructions in the file; then at the python prompt you enter the next two plays. You can put in almost any numbers as parameters at this stage: they don't have to be consistent with the rules of Nim.

# python -i test_nim_prompt.py
[3]
[3, 5]
[3, 5, 4]
[3, 5, 4, 4]
>>> nim_prompt(10,33)
[3, 5, 4, 4, 10]
>>> nim_prompt(9,24)
[3, 5, 4, 4, 10, 9]
>>> 

Alternately we can import the function

# python
Python 2.4.3 (#1, Apr 22 2006, 01:50:16) 
[GCC 2.95.3 20010315 (release)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from test_nim_prompt import nim_prompt
[3]
[3, 5]
[3, 5, 4]
[3, 5, 4, 4]
>>> nim_prompt(10,33)
[3, 5, 4, 4, 10]
>>> nim_prompt(9,24)
[3, 5, 4, 4, 10, 9]
>>> 

Right about now you've probably noticed that plays[] is a global variable, and that play is passed from main() to nim_prompt() only to be used to modify plays[], which is back in global namespace. You're wondering why I didn't just have plays.append() in main() rather than go through all this bother.

It would be quite reasonable to have plays.append() in main(). It turns out once I committed myself to the poor programming practice of using a global variable, I was on the slippery slope to programmer's hell and would have no choice but to engage in more poor programming practices. You can choose whether you have plays.append() in main() or in nim_prompt(), but here are some of the factors involved -

  • It is convenient to have all the code to write the prompt contained in the function nim_prompt(). Thus we can write and test the function in the file test_nim_prompt.py using a simple dummy main() (which just has global variable plays[]). This will allow us to later copy the function nim_prompt() into nim.py, with minimal changes (hence minimal chances of errors) to main() in nim.py.
  • All other things being equal (mainly readability), you want to get code out of main() into functions. Ideally you want main() to be short: main() will initialise variables and then make calls to functions. main() will always have some control logic neccessary to direct the flow of the program, so there will always be some code in there.
  • If ever we write nim_prompt() as an object, plays[] will be in the object (and not in main()). You will still need to pass play to the object. You may as well write main() now to pass play to nim_prompt()

As it turns out, we're going to accumulate enough code in nim_prompt(), that we'll be pushing it out into it's own functions as we write it.

2.6. Nim: better user interface: showing the counters

The next step is to write code to output the current number of counters. What we're going to output a symbol (say an "X") across the screen, one for each counter. Unfortunately we can't just print "X" in a loop. Here's two attempts showing that it can't be done

>>> last_number=15
>>> for x in range(last_number):
...     print "X"
... 
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
>>> for x in range(last_number):
...     print "X",
... 
X X X X X X X X X X X X X X X
>>> 

You can't turn off the carriage return (the first part of the example). Python inserts a blank even though you told it not to do so (the second part of the example). Doing something, even when you told it not to ,is part of the spec 6.6 The Print Statement, so there! You aren't allowed to complain about it. No other language presents you with this problem.

Let's try another approach.

>>> output_string = ""
>>> last_number=15
>>> for x in range(last_number):
...     output_string += "X"
... 
>>> print output_string
XXXXXXXXXXXXXXX
>>> 

You did remember the fencepost problem? How many "X"s did we get [134] ?

The string output_string will become part of our prompt. output_string will have a new value each time nim_prompt() is called. Where should we declare output_string: in main() or in the function nim_prompt() [135] ?

Copy test_nim_prompt.py to test_nim_prompt_2.py, insert the code which generates the "X"s into your nim_prompt() and check that you get sensible output. Here's my version of the code [136] and here's the output showing the number of counters left (along with the content of plays[] for debugging purposes) [137] .

You could run this program interactively (python -i test_nim_prompt2.py) and make further calls to the function nim_prompt() from the python prompt.

Note

You always save working code. You never obliterate working code by writing over it with new code.

If you're going to add/change code, you copy your last working file to a file with a new name. If you mess up, you can go back to your last working code.

You only add pieces of code in small enough chunks that you can get them going in the time you have. You don't want to have to walk away from non-working code, only to return in a few days time, to spend the first 30mins wondering what you were trying to do. Some people can write 100 lines of code and have it work the first time. If you can't write more than 5 lines of code and be reasonably confident that it will work, then only write code in chunks of 5 lines.

It's nice if you can write a piece of code independantly of your main code, and add it later as a function. This requires that the new piece of code doesn't need a whole lot of information/data from your already built piece of code. Modular programming requires that only small amounts of information (parameters, and return values) are passed to and from modules, so normally this isn't a limiting requirement.

The code to output the "X"s works. The code you've written is short (two lines) and no-one would quibble if you left it in nim_prompt() and continued coding. However we're going to add several more logical pieces to nim_prompt() and it's going to get quite long if we don't do something about it. Inside test_nim_prompt_2.py change the code, that writes out the number of counters, to a function show_counters(). show_counters() has these specs

  • it takes counters as a parameter
  • it returns a string of "X"s, one for each counter.

Write documentation for show_counters() and nim_prompt(). Remember that the documentation should be sufficient to let another person write code that does the same job, without them seeing your code.

Test that you get the same results when you run your code. Here's my code [138] and here's my output [139] .

If this piece of code (from the code above)

	output_string = ""
	output_string += show_counters(counters)

was changed so that the first line was deleted, what change would you have to make to restore the program to its original functionality [140] ?

2.7. Nim: better user interface: appending plays

Copy test_nim_prompt_2.py to test_nim_prompt_3.py. We don't need to send multiple plays to nim_prompt() anymore - we just need to know the state at the last play. In test_nim_prompt_3.py, change main() to the following.

#main()

#nim_prompt(3,56)
#nim_prompt(5,51)
#nim_prompt(4,47)
plays=[3,5,4]

nim_prompt(4,43)
# test_nim_prompt_3----------------------

This replaces the first 3 plays with the results of the first 3 plays, i.e. an updated version of plays[]. After the last play we still have

print plays
[3, 5, 4, 4]

Next we append, to the prompt, the strings showing the computer's plays and the player's plays. (We're going to do this in stages; the initial code will produce output that isn't quite correct, but we'll fix that as we go.)

We need to write out these plays with a p (for the player's play) or a c (for the computer's play), with the most recent play being appended to the string of "X"s first and the first play being appended last. Which player made the most recent play and how do we know [141] ? What code would we have to use to implement this (here in pseudo code) [142] ?

Add code to nim_prompt() to announce the player (computer/player) who made the last (most recent) play, using the length of plays[]. Print a "p" if is was the player; print a "c" if it was the computer. For now, we're not going to determine the number of counters picked up, only who made the most recent play.

Here's my test_nim_prompt_3.py [143] . When you ran it, did you get the correct output [144] ? Now we know whether to append a string of "p"s or "c"s, to the string of "X"s, to represent the last (most recent) play/entry in plays[].

Note
How can we know which player made the last (most recent) play, without knowing what they played [145] ?

The last entry in plays[] determines the first characters ("p"s or "c"s) to write after the "X"s in output_string. Next we determine the number of "p"s or "c"s to write. Looking through the methods/functions available for lists, we find that the only command available to look at the last entry in a list is list.pop(). This gives us the last entry and at the same time removes (pops) that entry from the list. This last property precludes the use of plays.pop() to write out the contents of plays[]. Why (what will be the state of plays[] after we've contructed the user prompt) [146] ?

The only non-destructive way to look at the contents of a list is for list_entry in list:. This looks from the first to the last entry in the list

>>> plays=[3,5,4,4]
>>> for play in plays:
...     print play
... 
3
5
4
4

However, however we want to read the list from the last to the first entry.

At this stage we have to solve

  • if we pop() entries off the list, we loose our list.
  • If we read out the list, it's read in the opposite order to what we want.

These are routine problems confronted by programmers. In the next two sections we develope a solution for each of these problems (we only need one solution; either solution will do).

2.8. Nim: better user interface: appending plays by reversing the list

Looking in the functions/methods available to work on lists, we find list.reverse(). This code reverses the order of a list with

list.reverse()

You don't have to assign the result to another list (although you can); the contents of the list are now in reverse order (this is called reversing in place). How would we use this to write out the plays in nim_prompt() [147] ?

Copy test_nim_prompt_3.py to test_nim_prompt_4.py. Start a new function reverse_append_plays()

  • move this code from nim_prompt() into reverse_append_plays().

    	#new code for this file
    	#who made the last play?
    	if ((len(plays)% 2) == 0):
    	        print "c"
    	else:
    	        print "p"
    

    Because we're moving code out of nim_prompt() into reverse_append_plays(), we're going to have to do some messing around making sure that parameters are passed, return values are picked up and that the new function is called correctly. This is a bit of surgery. It's not as simple as constructing a function in a completely separate file, with its own main(), which when you then copy into the program.

  • reverse_append_plays() uses plays[] where is plays[][148] ?
  • reverse_append_plays() returns a string containing the "p"s and "c"s for the players plays. Call the function with
    	output_string += reverse_append_plays()
    
  • In reverse_append_plays(), just before or just after you test the parity of the length of plays[], reverse() the order of the entries in plays[]. Make sure you unreverse plays[] before you exit from the function.
  • Determine whether the length of plays[] is odd/even and assign either "p" or "c" to a new variable player_char showing the identity of the last player
  • loop over the entries in the reversed plays[] list, determining the number of counters picked up at each play.

    Is the number of entries in plays[] known before you start looping? Do you use a for or a while loop [149] ?

    On the first iteration of the loop you know the identity of the player. Add the correct number of "p"'s or "c"'s for that player, to the end of result. Even though player_char will be wrong for the next player, allow the loop to run through all the plays, right back to the first play.

Here's my code [150] and here's the output [151] .

Note
You don't have to get code to work correctly on the first run - you just have to know what's wrong with it. First you get your code to run, then you get it to run correctly, then you get it to run fast. It's quite OK to have the wrong letters output for the players for the first attempt. You can however check that you printed the right number of letters.

Now we need to get the correct player_char as we iterate through plays[]. How might we do this?

The player_char (ex)changes each time we pick a new play from plays[]. Can you think of code that will look at the current value of player_char and set it to the other symbol (I used an if/else)? Write a piece of code (call it exchange_player_char.py) that starts with

player_char="p"

or

player_char="c"

and gives player_char the other value (this is called an exchange operation and is common in computing). Here's my code [152] and here's the output [153] .

Copy test_nim_prompt_4.py to nim_prompt_5.py and add in the code from exchange_player_char.py to give the correct output string for each play.

  • You will need to pass a parameter to exchange_player_char() and the function will need to return a result.

    Note

    Since exchange_player_char() is called by nim_prompt() you might expect that any variables declared in nim_prompt(), such as player_char would be visible in exchange_player_char() and these variables would not have to be passed as parameters.

    If you think this way (as I did), you'll get an error about local variables being referenced before assignment. A quick search with google finds the problem (see scope of global variables). Python scoping thinks that player_char in exchange_player_char() is a local variable and not the variable declared in nim_prompt_reverse(). Python can only access global variables and local variables (and a few others, e.g. built-in), but it can't access variables in calling functions.

    Never mind, programmers make wrong guesses all the time and don't worry if your guess as to what will happen isn't always right. This is one of the main ways you learn about a language.

  • Add documentation to exchange_player_char()
Note
You would normally leave this code in the calling function and not make a separate function. You're doing this because I'm going to show you a different way of doing the exchange and I don't want the two pieces of code to collide.

Look at nim_prompt_5.py to decide where you need to put the call to exchange_player_char(). Since python indents are white space (rather than sets of {}), it's easy to make mistakes with indenting. I commented out the printing of plays[] and the initial printing of output_string, since neither of these are needed for debugging anymore. Here's my code [154] and here's the output [155] .

Here's another way of changing player_char. Conditional statements slow down computers. It takes much longer to fetch instructions and data from memory than it does to execute them, so CPUs prefetch code in a pipeline, with all the code and data lined up ready to go. If a branch occurs, then one pipeline has to be thrown away. The time waiting to refill the pipeline slows down processing (known as a pipeline stall). The point is to have all instructions and data that will be executed in the future known now (so it can be fetched). Branching results in a pipeline stall.

In an interactive game like we're playing here, speed is not a concern. As well, since we're doing it in python, we aren't worried about speed either.

However if you wanted to avoid a conditional statement, here's how you'd do it.

First calculate the results of both branches (both the results if the number of plays is odd and is even). It turns out that calculating both branches (if they're short) of a conditional is faster than finding that you have the wrong one and having to wait for the pipeline to refill. In our case, the two branches are short and the results are simple (the result is a "p" or a "c" or for the other possibility "c" or "p"). In both cases ("p", "c"), when you first calculate player_char, calculate the other value as well.

	if (player_char == "p"):
	        not_player_char = "c"
	else:
	        not_player_char = "p"

Now after outputting each set of player_char (the first set being "cccc"), you swap player_char to the other character. You don't have to keep track of which character is being output; you just have to tell the program to swap the character after each play. Here's the swapping code (standard code for exchanging the value of two variables).

	temp = player_char
	player_char = not_player_char
	not_player_char = temp

Copy test_nim_prompt_5.py to test_nim_prompt_6.py and incorporate the new code (and comment out the old code). Here's my version of the code [156] . The output is the same as before.

Copy nim.py to nim_2.py. Copy the functions from test_nim_prompt_6.py into nim_2.py and modify main() to use your functions, using main() from test_nim_prompt_6.py as a guide as to what must be added to your program.

Do you need to display the new prompt after the both player's plays? Does the computer need to see the prompt after both plays? Does this help the user? Try nim_2.py only displaying the new prompt once for the pair (player/computer) of plays. What happens to the prompt if you do this [157] ? What's the problem and how do you fix it [158] ?

Here's nim_2.py [159] .

In the new prompt line, can you easily see the difference between a "p" and a "c"? If not change the output so that you can (e.g. make one symbol an UC). Copy nim_2.py to nim_3.py. I did a bulk find and replace of "p" to "P".

2.9. Nim: better user interface: appending plays by popping the list

In the previous section we handled the problem of having no instruction to read the list plays[] from the back to the front, by reversing the list and reading the reversed list from the front to the back.

Here we look at ways of reading the list plays[] when pop()ing a list, removes the entries, thus destroying our record of the game. The solution is to make a copy of plays[] and pop() the entries off the end of the copy. At each step we know the length of the copy and hence who made the last play.

The next question is whether to build the new code inside a copy of nim_2.py or a copy of test_nim_prompt_6.py. If we build inside nim_2.py, to test the code, we'll have to play the game each time. However the amount of added code is small (so we won't have to play the game for long to test our code) and we won't have to do surgery transferring code from test_nim_prompt_6.py.

Inside nim_3.py make a copy of reverse_append_plays() as pop_plays(). and in the copy of the function, change all occurrences of the string nim_prompt_reverse to nim_prompt_pop. Now modify nim_prompt_pop()

  • removed the commands that reverse()'ed (and unreverse()'ed) plays[]
  • make a copy of plays[], from which you will pop() data to construct output_string. Use the following construct:
    copy_of_list = list[:]
    
    this makes a new list. The intuitively obvious construct
    copy_of_list = list
    
    doesn't work. It makes both names copy_of_list[] and list[] point to the same list (what you really need, huh?). Give the copy a name that will show the reader what it holds and that it won't be around for very long: temp_copy_plays[] would do it.
  • Since you know the number of elements in temp_copy_plays[] you can use either a while or a for loop to iterate through the list of plays.

    In earlier languages, where speed was paramount, you used a stack to hold a list. There was no instruction to determine the size of the list/stack, and usually you'd pop() off elements till the stack was empty (it's much faster to pop a stack than to pop a list). You'd use a construct like this

    	while (last_char=stack.pop()):
    		output_string+=last_char
    

    When there were items in the list, the assignment instruction last_char=stack.pop() would succeed, and the line would become while(succeed) and the loop would execute. When the list became empty, the assignment would fail and the loop would not be executed.

    This construct doesn't work in python; it has a different error reporting mechanism.

When you're done playing with the code, leave nim_prompt() calling either reverse_append_plays() or pop_plays().

Here's my code [160] .

2.10. Nim: better user interface: Legend line

The last piece of the new prompt is

    '    1    '    2    '    3    '    4  ^ '    5    '    

Where in nim_prompt() do we know the length of this string and the position of the "^" [161] ?

Write a piece of code (call it test_legend.py) with the following specs

  • has a main() with the following code
    counters = 43
    legend_length = 59
    legend(legend_length, counters)
    
  • has a function set_legend(length, last_counter), which returns a string legend_string. legend_string will have length characters.
    • all characters will be blanks except
    • positions divisible by 5 will be "'"
    • positions divisible by 10 will be the number with the "0" removed
    • the symbol at position last_counter will be a "^".

    Hint: how do you write a string containing 59 blanks?

    If you write the characters in the order above, then subsequent writes will (conveniently) overwrite earlier writes: i.e. if you write a "'" at position 5,10,15..., then when you write a "1" at position 10, the "'" at position 10 will overwritten, giving the required output string.

Where will the code in test_legend.py:main() go when you merge test_legend.py with nim.py [162] ?

There's a minor wrinkle here. In earlier languages, strings were arrays of characters, where every character could be individually manipulated/written/read. In object oriented languages (like python), strings are constants which you can only read. The string.append() (or pop()) function/method generates a new string as part of the operation and throws the old one away. If you want to do operations on individual elements you need a list[]. After you have the characters in your list[], you copy the contents to a string.

Here are some list[] methods that will help with making the legend (from An Introduction to Python Lists. http://effbot.org/zone/python-list.htm). All of the list operations modify the list in-place. If you haven't done so already see the info in programming with lists.

  • 	list.append(object)	#add an object (eg a string) at the end of the list
    
  • 	list[index] = object	#put object at position index in list (index starts at 0)
    
  • If a list contains strings (as our list does), you can combine the strings in the list into a single long string, using the join string method:
    	string = ''.join(list)
    
    This will output your list (containing strings) as a string.

Assemble your output in legend_list[] and then convert the list to a string. Here my code [163] and here's the output

# ./test_legend.py
0    '    1    '    2    '    3    '    4  ^ '    5    '   

Copy nim_3.py to nim_4.py and merge test_legend_code.py with nim_4.py and test it out. Do your numbers line up correctly with the string above it? If not, what's the problem and what's a fix for it [164] ? Try both to see which you like. Fix your code. (Even at the end, there are fiddly little things, you hadn't thought of, that need to be fixed. A lot of thing don't quite work properly. It always happens.)

Here's my code for nim_4.py [165]

2.11. Nim: Imperfect opponent

People will quickly give up playing an opponent that wins every time (and you won't get any money from people buying your games). In that case, we have to write a version of the code where the computer sometimes makes mistakes.

We'll change the code so that the game has 3 levels

  • level 0 - expert - plays perfectly
  • level 1 - medium - makes about 1 mistake every 5 plays (this should be enough to let a player make one mistake and recover)
  • level 2 - dummy - all plays are random

Initially we'll hard code the level of play into the game, then later we'll let the user control the level.

Currently the computer's turn is handled by computers_turn(), which makes perfect plays. We'll change the code to first call another function play_at_level(). This code will decide whether to pass the call on to computers_turn() or to a new function which makes mistakes.

How do we get the program to make mistakes? If we want the computer to make a bad play, we tell the computer to pick a random number in the range of allowed counters. Occasionally this play will be right, but it will only be right by chance.

If we only sometimes want the computer to play badly, say 20% of the time, what do we do? We let it first decide if it's going to make a good or a bad play: it first picks randomly amongst 5 numbers (say 0..4). If the computer picks a selected number (let's say we make the number 0, which it will pick 20% of the time), then for that play, the computer makes a bad play, otherwise it plays perfectly. How would we do the coding if we wanted the computer to play badly 80% of the time, 10% of the time [166] ?

So there are two steps; the first to work out whether to make a good or bad play, the second to calculate the play.

Looking up Generate pseudo-random numbers (http://docs.python.org/lib/module-random.html), here's a useful function

# python
Python 2.4.3 (#1, Apr 22 2006, 01:50:16) 
[GCC 2.95.3 20010315 (release)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import random
>>> random.randrange(4)
3
>>> random.randrange(4)
2
>>> random.randrange(4)
3
>>> random.randrange(4)
2
>>> random.randrange(4)
1

randrange() has the same syntax as range(). random.randrange() takes upto 3 parameters ([start],stop,[step]), (so you can start the range at 1 if you want, rather than with 0 as I did in the example above). The default values for the optional parameters (the ones with []) are start=0, step=1.

Now we write a skeleton for some code: start a file set_level.py with the following specifications. (You aren't starting real coding yet - put comments where you don't have code. The code must run, but doesn't have to do anything useful at this stage.)

in main()

  • set max_num=9 (this is the maximum number of counters you can pick up).
  • set counters=43, the number of counters on the table
  • have a variable level, to determine how well the computer will play, which you will initially assign the value 0,1 or 2
  • you will need to call a function, play_at_level(), which will take as parameters level and max_num. play_at_level() decides whether the computer should pick a random number of counters or the optimal number of counters.

in play_at_level()

  • Currently the computer's turn is made by computers_turn(). When we merge set_level.py with nim.py, computers_turn() will be renamed to computer_play_expert() and will be called by play_at_level(). Find the parameters that compute_play_expert() needs and make sure that they are also passed from main() to play_at_level().
  • play_at_level() will also call another function computer_play_random() (which we will write shortly). Assume that computer_play_random() will need the same parameters as computer_play_expert() (it's just a guess right now, but if we're wrong, we'll fix it later).
  • Find the type (string, int...) of return value from computer_play_expert() (computer_play_random() will return the same type of variable). This variable will now be passed back to play_at_level(). In nim.py the call to computers_turn() will be replaced by a call to play_at_level. Have play_at_level() return the same type of variable to main() as computers_turn() did.

This is the original code (schematically). It calls computers_turn() which plays perfectly.

#functions
def computers_turn(counters,max_num):
	result = int 	#number of counters picked up
	return result

#main()

max_num=9
counters=43

number_counters=computers_turn(counters,max_num)

Let's look at what has happened when we insert play_at_level in the call chain to intercept the call to computers_turn()

  • compute_play_expert() is passed parameters (counters, max_num); the same interface as computers_turn() had.
  • play_at_level() needs the extra parameter level. The definition will then be play_at_level(level, counters, max_num).

Here's my skeleton. This is perfectly good code: it runs, though it doesn't do a whole lot yet. Comments will become the fully functional code.

#! /usr/bin/python

def play_at_level(level, counters, max_num):
	
	#result = computer_play_expert(counters, max_num)	#returns an int, the number of counters to pick up
	#result = computer_play_random(counters, max_num)	#returns an int, the number of counters to pick up

	result = 0	
	return result

#main()

max_num=9
counters=43
level=0

turn = play_at_level(level,counters,max_num)
print "the computer picked up %d counters" %turn
# set_level.py ----------------------

Get your version of this code to the stage where it runs.

Next copy set_level.py to set_level_2.py set up conditionals in play_at_level() so that

  • there is a commented call to computer_play_expert() for (level==0). Return a dummy number of counters e.g. with a line like
    result = 8
    
  • there is a commented call to computer_play_random() for (level==2). Return a different dummy number of counters, as you did for computer_play_expert().
  • For (level==1) write some commented skeleton code to show that you'll be using the result of a random function to decide which of the two computer_play functions to call.
  • Add a branch to print out an error message if the value of level isn't a 0,1 or 2. You will now have 4 branches - branches for 0,1,2 and a branch for anything else.

    Note

    If you expect a piece of code to handle only a limited range of values (e.g. 0,1,2), then make sure you have a statement to handle any other values (to handle what is called an "out of bounds" condition). Your code should handle from -∞ to +∞, no matter which values you expect.

    You always include code to check user input, so we will be including checking code to only allow the user to enter 0,1 or 2. (We had similar checking code in players_turn() to only allow the player to pick up an allowed number of counters). The length of the chain of code, from the user input to the value of level being used here is short and we don't expect any code to mess up the value of level.

    However in other situations, the code path could be long with plenty of chances for code to mess up the expected value. In this case, the extra conditional statement will pick up incorrect code somewhere else.

    Quite what you do with an out of bounds conditions is something else. You're just starting development on this piece of code, and will be watching the screen carefully. In this case, just sending a notice to the screen is enough. After the code is working, and where no-one will be watching the screen, then you should at least send the notices to a log file, which can be inspected following a crash. Other things to do are to force the code to exit (after writing to a log file). This will get people's attention and you'll soon find out about the problem. Forcing the code to exit is fine for non mission-critical code, but you can't blow up a rocket on an error like this. You'll have to handle it and handling it may take quite a bit of code.

  • Inside each branch, put a debugging print statement to let the user know which level is being run. Indentify the routine that's doing the printing like this
    print "play_at_level: information...."
    
    so that you know which routine's debugging statements you're reading (you should expect to handle debugging statements from several routines at once).

Here's my code [167] . Check that your code works for all levels (e.g. try level 5,9...).

Note
While if, elif, else statements are fine for small numbers of cases, if statements don't scale well - the construct would be hard to read if we had 10 levels to handle. In other languages there is a case statement to handle such situations, but python doesn't have this.

Copy set_level_2.py to set_level_3.py.

  • Add code for (level==1) so that the program calls computer_play_random() 20% of the time and computer_play_expert() 80% of the time.
  • Whenever the computer chooses to make a bad play, print out a statement like (e.g. "Oooh, this is hard").
  • When the computer is making random plays (level==2) print out something that a person who doesn't know the right stradgety might say.
  • For each branch, return a different result (number of counters).
  • Notice that you have a print "play_at_level: %d" %level in all branches. Move this line out to before the conditional branch. Leave the error statement in the last branch.

Set (level==1) and show that your code makes bad plays about 20% of the time. Here's my code [168] .

Copy set_level_3.py to set_level_4.py. Currently we have a commented call to computer_play_random() as the code for the computer to make a bad play. What code is needed to make a bad play and what information does this code need from the rest of the program [169] ?

Will this work if counters < max_num)? If not fix the code [170] .

This is one line of code. Does it need to be written as a function [171] ?

We initially assumed we'd need a function to handle a bad play, but we've just found we don't. Use this piece of code in set_level_4.py to make the bad plays. Check that you get random answers about 20% of the time for level==1 and all the time for level==2. Here's my code [172] .

This code is now functional except for the one line of commented code, a call to computer_play_expert(). Where will this code come from [173] ?

Copy nim_4.py to nim_5.py Make a list of the things you'll need to change in nim_5.py to merge set_level_4.py [174] .

Don't be too upset if your code doesn't work first time (mine didn't - I forgot some of the things above and there were some bugs in the code, which I fixed in the notes, so you won't see them). Try a few rounds at each level. Check that the computer plays badly when it should play badly and that you can recover when the computer makes a mistake.

3. Top Level Down (top down), Bottom Level Up (bottom up) Design

There are two styles of coding, top down and bottom up (see Top Down and Bottom Up Design http://en.wikipedia.org/wiki/Top-down).. These two terms are commonly used in programming. You need to know what they mean, only so you'll know what other people are talking about, not because it's going to affect the way you code. The design method we've been using so far is bottom level up. The characteristics of bottom up coding are

You have a problem to be coded up. You start with the smallest part of the problem that can be coded, and you write fully functional code for it. You do this initially without much of an idea about coding the rest of the problem. Your assumption is that everything can be coded and you'll handle it when you get there, or if it can't be coded up, then that part of the problem will be left unhandled or handled later. Then you tackle another part of the problem, almost certainly a piece of code that interacts with the code you've just written.

In the nim game, your first piece of code would be the announcement of the rules, your next piece of code would record the player's play, the next piece of code would check that the player make a legal play... This process continues till all the code is written. You will very likely modify some or all of your earlier code in light of things you learn on the way.

Top Down coding

You analyse the whole problem and break it into logical blocks. These will become functions, or functions calling other functions. You figure out the information that the blocks need to function; these will become parameters for functions. You figure out the information the blocks will generate; these become return values. How deep you go down the logical tree is up to you: the blocks could be large (a function which calls lots of other functions, which you don't describe) or small (a function with only a few lines of code).

You check that the blocks when working together, do handle problem to be solved, by coding up stubs. A stub is a function that has limited functionality, but looks like a real function to the calling code. The stub for announcing the nim rules would just be the line

print "Nim: Here are the rules"

You would write the requirements for the function as comments, so that later (possibly months later, when you've long forgotten what the function is supposed to do), you (or someone else) writes the real code. The stub for the player's turn might just be

return 5

to indicate that the player picked up 5 counters. The player would always pick up 5 counters, no matter what the state of the game. You'd write the real specifications in comments.

Once you have all the stubs written, you run them as your first attempt at the program to check that the logic is correct and look for obvious design flaws. Then you start coding by turning the stubs into real functions.

Whether you use top level down or bottom level up, is up to you. I program bottom up. It seemed the only way for me, since I started coding with small examples and built up from there. I didn't hear of top down and bottom up programming for my first 30yrs of coding. No matter which style of programming you're most comfortable with, you'll always be doing both aspects. The bottom up programmer has to understand the whole problem enough to break off logical blocks to code. The top down programmer has turn all the stubs into functions.

In big projects with multiple programmers, the first step will be a top down design, after which pieces will be handed off each programmer. The pieces may be quite large and the programmer will then use their own approach (bottom up, top down) to analyse their part of the problem and start coding.

The concept of top down and bottom up is not restricted to programming, although it may have different names in other fields. In large engineering projects (building, freeway, dam, bridge), someone must do a top down design, figuring out the time and costs, put in a bid, get an order from the customer, all before the first brick is moved. Only then will subcontractors be called to handle the individual parts of the project.

One thing that goes wrong with top down, is that stubs written to handle unusual situations are left as stubs. You hear stories of badly behaving code and after much work to reproduce the problem, the maintainer finds a one line stub. This doesn't happen with bottom up coding, as the coder usually handles all the cases at one time, writes the tests and after checking that it all works, submits the code.

While there are no logical problems in top down design, once the design is handed out for implementation, it turns out that the management of large numbers of programmers is famously complex and characterised by catastrophic disasters. The causes of these disasters and the futility of the methods used are well understood The Mythical Man Month (http://en.wikipedia.org/wiki/The_Mythical_Man-Month), but are apparently beyond the grasp of most managers (or at least not amenable to current management methods). Anyone who intends to program for others (or work in teams) should understand the lessons of this book. You may think the book is so trivially obvious that you'll wonder why I even suggest that you read it. If so, I'm very glad, but be aware that you won't be meeting many people like yourself. After one of these expensive and highly visible projects are canned, the well compensated management will look at each other and say in wonderment "we did all the right things and still it didn't work" (there are people who will believe this).

4. Prime Numbers: The Sieve of Eratosthenes

http://www.scriptol.org/sieve.php#python http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes (watch graphic, we actually use the Sieve of Atkin) http://www.fermi.franken.de/wschildbach/primes.html Ulam Spiral http://ourworld.cs.com/ttpi314159/index.htm?f=fs php implementation, finding location (x,y) of a prime http://mgccl.com/taxonomy/term/20/0?page=3 http://krenzel.info/?p=81 http://www.ocf.berkeley.edu/~wwu/riddles/cs.shtml#numberSpiral

5. Perfect Numbers

5.1. Perfect Numbers: order of the algorithm

5.2. Perfect Numbers: run once

Some programs are run many times. Some programs you only run once. They do a lot of calculations, that you wouldn't want to do by hand, but once you've run it, you don't need to run it again. The perfect_numbers is such a program. So what if the algorithm is slow, you can let it run for a week.

6. Cryptography

Boris Hagelin

7. Recursion

Make a controller for a car. Have subroutines that, speed the car up by 1mph if below a set speed (eg 60mph), slow the car down by 1mph if above a set speed (eg 70mph), do nothing if in an acceptable band (eg 60-70mph). Start the car at a speed, use an infinite while loop to control the car (ctrl C to exit). Add a rocket that boosts the car's speed at random times, and by a random amount if in the steady speed band.

Change the routines to loop until they reach the required speed and then return

Change the routines to recursively call themselves till they reach the required speed and then return.

8. Projects

Nial Ferguson, The Ascent of Money: look at the chapter on insurance. Do an event driven simulation.

Do an event driven simulation of a pair of two lane roads at a set of traffic lights. Do two intersections separated by 100yrds. See if synchronising the lights helps.

Give "." the date of the latest file in the directory, to allow a backup to not to have to search any deeper.

There are $5 3-axis USB accelerometers which can be used to test for movement. Get these to work and make a webpage using rrdtool to monitor the movement of something (eg fridge door).

9. Examples

code up the day of the week for your birthday

10. Back to basics: Base 256

Note
some of this material comes from how to obscure a URL (http://www.pc-help.org/obscure.htm).

The maximum ibase for bc is 16; the maximum obase is 256. Since we don't have an agreed upon ordered set of 256 symbols, bc outputs base 256 numbers in decimal, separated by spaces.

echo "obase=256; ibase=2;  10000000" | bc
 128
echo "obase=256; ibase=2;  11111111" | bc
 255
echo "obase=256; ibase=2;  100000000" | bc
 001 000

echo "obase=256; ibase=10;  256" | bc
 001 000
echo "obase=256; ibase=10;  256*16" | bc
 016 000
echo "obase=256; ibase=10;  256*256" | bc
 001 000 000

echo "obase=256; ibase=16;  100" | bc
 001 000
echo "obase=256; ibase=16;  400" | bc
 004 000
echo "obase=256; ibase=16;  1000" | bc
 016 000
echo "obase=256; ibase=16;  F000" | bc
 240 000
echo "obase=256; ibase=16;  FFFF" | bc
 255 255
echo "obase=256; ibase=16;  10000" | bc
 001 000 000

Computers linked by a network are identified by a unique unsigned 32 bit number (called an IP). The IP on your machine can be seen in base 256 by running the command ifconfig (unix, cygwin) (or possibly /sbin/ifconfig) or ipconfig (windows). The IP for the machine I use to write these class notes (in base 256 notation) is 192.168.1.3. The notation is called "dotted quad" (4 numbers separated by dots).

dennis:~# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:A0:CC:56:9B:6A  
	  inet addr:192.168.1.3  Bcast:192.168.1.255  Mask:255.255.255.0
	  UP BROADCAST RUNNING ALLMULTI MULTICAST  MTU:1500  Metric:1
	  RX packets:21532458 errors:1 dropped:0 overruns:0 frame:0
	  TX packets:15254713 errors:4 dropped:0 overruns:0 carrier:4
	  collisions:0 txqueuelen:1000 
	  RX bytes:3417783082 (3259.4 Mb)  TX bytes:3357752091 (3202.2 Mb)
	  Interrupt:11 Base address:0xdc00 

The base 256 notation is easier for humans to use than a 32-bit number particularly since on any network the first 3 base 256 numbers don't change (in this example 192.168.1, only the last number is different for each computer). What is the decimal value for this IP (192.168.1.3) [175] ?

The IP for a fixed machine will be assigned by a sysadmin from free addresses on the network. Machines that connect only now and again and at different places (e.g. laptops) are given a temporary IP from a small pool by a machine already on the network (called a dhcp server). In dhcp language, the laptop is "leasing" the IP. Since the dhcp server doesn't know when the laptop will disconnect, the laptop is given the lease for only a short time (5-30mins) and has to renew its lease by the end of the lease period or loose its connection to the network. Users are not and need not be aware of any of this. Users just connect the ethernet cable to their ethernet card, or plug in their wifi card and programs already installed on the laptop handle everything else.

Find the IP on your machine. Then remove your wifi card (on windows you'll have to tell the machine that you're about to pull the card) and see that you loose that IP. Plug the wifi card back in and see that you get an IP back (probably the same one).

The notes for this class are being served by a machine with IP=192.168.2.254. You surf to this IP by using a name rather than a base 256 number (easier for humans). In this case the name is router.masp.net, a name that's only known privately on my network. The translation between names and IP numbers is done by the computer equivalent of a phone book (called DNS). Check that you can get to the class notes at http://router.masp.net/python_class/ and http://192.168.2.254/python_class/

What's the decimal equivalent of the base 256 number 192.168.2.254 [176] ?

Hexadecimal will require twice as many digits as base 256. What's the hexadecimal equivalent of the base 256 number 192.168.2.254 [177] ?

In earlier times, web browsers were quite happy to surf to http://3232236286/, but most don't allow it anymore to stop phishing (IEv5 did it). Still the decimal number is a valid IP. Try ping'ing the IP using some different formats for the IP.

ping router.masp.net
PING router.masp.net (192.168.2.254): 56 octets data
64 octets from 192.168.2.254: icmp_seq=0 ttl=64 time=2.1 ms

ping 192.168.2.254
PING 192.168.2.254 (192.168.2.254): 56 octets data
64 octets from 192.168.2.254: icmp_seq=0 ttl=64 time=2.2 ms

ping 3232236286 
PING 3232236286 (192.168.2.254): 56 octets data
64 octets from 192.168.2.254: icmp_seq=0 ttl=64 time=2.4 ms

Not everything is as you might expect. Here's the IP in hexadecimal

echo "obase=16; ibase=10;  192*256*256*256 + 168*256*256 + 2*256 +254" | bc
C0A802FE

Seeing that the decimal version works, a reasonable person might expect that some of these would work.

ping C0A802FE
ping: unknown host C0A802FE

ping 0xC0A802FE
ping: unknown host 0xC0A802FE

ping 0xC0.0xA8.0x02.0xFE
ping: unknown host 0xC0.0xA8.0x02.0xFE

These might have worked some time in the past, but it seems that code writers have trapped any attempts to use addresses that are more than 32 bits. You might think that the numbers beyond 32 bits would overflow and the remainder be used as the real address, but the numbers are all recognised as being invalid.

#ping an address more than 32 bits
ping 1.192.168.2.254
ping: unknown host 1.192.168.2.254

#calculate an address that's 1 base 256 digit more than a known IP address
#since hex adddresses don't work for ping, you wouldn't expect much success
echo "obase=16; ibase=10;  1*256*256*256*256+ 192*256*256*256 + 168*256*256 + 2*256 +254" | bc
1C0A802FE
ping 1C0A802FE
ping: unknown host 1C0A802FE

#try it in decimal; this at least works for a valid IP.
echo "obase=10; ibase=10;  1*256*256*256*256+ 192*256*256*256 + 168*256*256 + 2*256 +254" | bc
7527203582
ping 7527203582
ping: unknown host 7527203582

Find the IP of your machine, ping that IP (in dotted quad format), then ping the IP in decimal format. Convert the decimal IP back to dotted quad format [178]

11. Some Trigonometry

11.1. sin(),cos(), tan(), radians

There are 3 basic trig functions; sin(), cos() and tan() (see Trigonometric Functions http://en.wikipedia.org/wiki/Cosine). These functions describe the ratios of the sides of a right triangle. Here's a right triangle with an angle of 30° (this would be the side view of a cone with angle of repose of 30°).


	    t .|
	 o .   |
      p .      | opposite (height)
   y .         | 
h .  30)       | 
 --------------
   adjacent (radius)

The definition of these 3 trig functions is based on a right triangle, and is

sin() = opposite/hypoteneuse
cos() = adjacent/hypoteneuse
tan() = opposite/adjacent

The tan() is commonly known as the slope (in this case of the hypoteneuse). In a right triangle, once you've chosen two of the sides, the other side is determined (by Pythagorus), so once you know the value for any of these functions for any angle, the value for the other functions is determined.

Probably the best known trig function is the sin() (see the red and green graphs in the section Unit circle definitions http://en.wikipedia.org/wiki/Cosine#Unit-circle_definitions, labelled "sin and cos in the Cartesian plane"), also see Sine wave http://en.wikipedia.org/wiki/Sine_wave). The sine wave describes oscillatory phenomena and the projection of an object moving in a circle, e.g. the position of a pendulum, the pressure in a sound wave, the electric field in an electromagnetic wave (e.g. radio, light), the position of the tip of a propellor.

What is sin() for the triangle below with an angle of 30°, which will have the dimensions as shown 90° [179] ?


	    t .|
length=2 o .   |
      p .      | opposite, length=1
   y .         | 
h .  30)       | 
 --------------
   adjacent

What is the length of the adjacent side [180] ?

Now that you know the lengths of all of the sides of this triangle, what is cos(30), tan(30) [181] ?

By drawing the appropriate triangle, find tan(45) [182] ?

What is sin() for the triangle below as the angle approaches 0°, 90° [183] ?


	    t .|
	 o .   |
      p .      | opposite (height)
   y .         | 
h .  angle)    | 
 --------------
   adjacent (radius)

While sin(),cos() vary continuously, tan() reaches ∞ at 90°.

Any math calculator will have these functions. The argument (i.e. the angle) that the functions take, can be in degrees (the everyday unit) or in radians (the mathematician's unit). You can flip from one measure to the other: a circle has 360° or 2π radians.

Radian measure is convenient for many functions involving trig. The "2π" come about because a circle has a circumference C=2πr. A semi-circle has a circumference of πr. This allows us to define an angle as the ratio of the arc of the circumference divided by the radius. With a circle having 2π radians, then a quadrant of a circle (90°) subtends an angle at the centre of π/2 (pronounced "π on 2").

     .
    |   .
    |     .
    |      .
    |pi/2   .
    --------

A mathematician cutting up a pie for desert, will ask the other mathematicians what they want. They'll reply "I'd like π on 2" or if they're not particularly hungry "I'd like π on 3 please". Any non-mathematician at the dinner, not understanding the difference between pie and π might be alarmed at hearing several requests for "π on 2", thinking there will be no pie left.

Python's default unit for angles is radians

igloo:# python
Python 2.4.4 (#3, Mar 30 2007, 19:33:13) 
[GCC 3.4.6] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from math import *
>>> radians(45)
0.78539816339744828
>>> tan(radians(45))
0.99999999999999989

Using python, find sin(30), cos(60), sin(0), cos(0) [184] .

The discovery, through calculus, of quickly converging series to calculate sin(),cos() and tan(), were a great aid to surveyors, who changed the social structure of Europe by dividing it into owned plots of land. The US president George Washington was a surveyor by trade.

11.2. Angular diameter of the sun and moon; annular eclipses

The distance from the earth to the sun is 146Mm. The diameter of the sun is 1.38Mm. What is the apparent diameter of the sun in degrees? Assume the size of the sun is the length of the opposite side, while the distance to the sun is the adjacent side. First find the tan() of the apparent diameter of the sun. Here's what the calculation looks like in python

dennis:/src/da/python_class# python
Python 2.4.3 (#1, Apr 22 2006, 01:50:16) 
[GCC 2.95.3 20010315 (release)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from math import *
>>> 1.38/146
0.009452054794520548 	#tan() of the angular diameter of the sun
>>> atan(1.38/146)
0.0094517733231954185	#the angle (in radians) of the diameter of the sun
>>> degrees(atan(1.38/146))
0.54154672033343809 	#the diameter of the sun in degrees
>>> 60*degrees(atan(1.38/146))
32.492803220006287 	#the diameter of the sun in minutes

diameter of sun=32.49min

Doing it this way, the triangle isn't quite a right triangle (the angle is 89.75°, which is close enough for what we're doing). If the angle wasn't so small we'd do it this way.

	          .|
	      .    | d
	  .        | i 
      .            | a
oo)----------------  m 
      .dist to sun |
	  .        | s 
	       .   | u
	          .| n

The diagram shows a viewer looking at the sun, with the line to the center of the sun and the diameter making a right triangle. You first calculate the radius of the sun (half the diameter) and calculate the angle subtended by half the sun. You double this to get angle subtended by the sun.

>>> 1.38/(146*2)
0.004726027397260274	#tan of half the diameter of the sun
>>> atan(1.38/(146*2))
0.0047259922119301497	#angular size of half of the sun, radians
>>> degrees(atan(1.38/(146*2)))
0.27077940775529408	#angular size of half of the sun, degrees
>>> 2*degrees(atan(1.38/(146*2)))
0.54155881551058815	#angular size of the whole sun, degrees
>>> 60*2*degrees(atan(1.38/(146*2)))
32.493528930635293	#angular size of the sun, minutes

diameter of sun=32.49min

For small angles, the sloppy and the exact result are the same.

A handy reference for locating stars, planets and constellations in the sky, or the distance between mountains or ships on the horizon, is the distance across the knuckles of your hand when your arm is extended. This angular distance doesn't change much as the body grows and changes size. Measure the distance from your shoulder to your knuckles, then the distance across your knuckles. Find the (approximate) angle between your knuckles as viewed by your eye [185] .

I have relatively small hands. The usual figure quoted is 8°. The distance between pairs of knuckles is 2°. How many sun widths can you fit between a pair of knuckles [186] ? You can fit 12-16 sun diameters across your outstretched knuckles.

Example: you're standing on with a clear view of the horizon and see the sun in the west about two hand (i.e. across the knuckles) widths above the horizon. Actually you need to measure the distance along the line of the ecliptic, which being inclined to the vertical, will be a bit longer than the vertical, but lets ignore this detail for this exercise. How long is it till sunset [187] ? It's mid september. What time is it [188] ?

Example: The diameter of the moon is 3474km and its distance from the earth varies from 363,104km (apogee) to 405,696km (perigee) (see Moon http://en.wikipedia.org/wiki/Moon). What is the range of the angular size of the moon (in minutes) [189] ?

The moon varies about 10% in diameter during its orbit about the earth (you won't notice this variation by casual observation). The moon rotates at constant angular velocity about its own axis, but because of the elliptical orbit, the speed at which the moon moves through the sky varies through the month. and the moon appears to oscillate from side to side about its axis, called libration, presenting more than half the moon's surface to viewers on the earth (we can see about 4/7 of the surface of the moon over a month). For an animation of libration see Libration (http://en.wikipedia.org/wiki/Libration) (note the prominent crater Tycho, http://en.wikipedia.org/wiki/Tycho_(crater) in the southern highlands). For view of the moon's differing size in the same orientation, see shots of the moon taken over a period of 2 years Libration: 2 years in 2 seconds (http://pixheaven.net/voir_us.php?taille=grand&mon=0505-0704).

By a fantastic coincidence, the moon is 400 times closer than the sun and is 400 times smaller than the sun. This means that the moon can exactly eclipse the sun. Because of the ellipticity of the moon's orbit, a viewer on the earth's surface can see a total lunar eclipse or an annular lunar eclipse. A total lunar eclipse occurs when the moon covers the face of the sun. An annular lunar eclipse occurs when the moon doesn't quite cover the face of the sun and a ring of the sun is seen around the moon. Because of the angular size of the moon and sun are so close, the sun's annulus only shows between the mountains of the moon, leading to a series of bright points around the moon, known as Baily's Beads (http://en.wikipedia.org/wiki/Baily's_beads). Here's the angular size of the sun and the moon at apogee/perigee

diameter of sun                    32.49min
diameter of moon at apogee         29.44min
diameter of moon at perigee        32.99min

Is it possible to have a total lunar eclipse, an annual lunar eclipse at apogee, perigee [190] ? Note how at perigee the moon is only just big enough to fully cover the sun's face. Most total eclipses are annular.

You'll be lucky to see a total solar eclipse once in your life. The path is only a couple of 100m wide, so you have to be exactly at the right spot to see Bailey's Beads (offer your assistance to a bunch of astronomers - they'll know where to go). The eclipse I saw was on 30 May 1984. We followed the weather forecast from a wet and cloudy Maryland, phoning a friend at NOAA in Colorado every couple of hours for updates - as to how far south we'd have to go (this is before the internet and before weather.com). We had to go about 2 states further south than we'd expected, to set up our telescopes in a large empty parking lot outside a church under a brilliant clear sky, in Georgia (we'd phoned ahead for permission). We set up a line of about 8 telescopes equipped with TV cameras, at about 10m intervals (to make sure at least one of them saw Bailey's Beads), transverse to the path and let the cameras roll. To synchronise the time (again before the internet), we used radios tuned to the time signal at WWV, and let the TV camera microphone pick up the time signals. The leader of the party set up in the middle of the line, to get the best view of Baily's beads, but his calculations were off by about 10m and the next telescope in the line got the best view.

The next total solar eclipse in NA Solar Eclipse 21 Aug 2017 (http://eclipse.gsfc.nasa.gov/SEanimate/SEanimate2001/SE2017Aug21T.GIF). passes right over our heads here in NC. It will be a long eclipse (by eclipse standards) - 2min:43sec (the peak for this eclipse is 2:45 just a bit west of us). All your friends will be coming to see it. People you'd forgot were your friends will be arriving to see you. Don't miss it! (Let's hope it's a clear day.)

11.3. Angles of the Great Pyramid

Let's find a few angles in the Pyramid of Khufu. Here's an ascii art diagram of the pyramid from above (the plan view http://en.wikipedia.org/wiki/Plan_view) and from the side (the elevation view http://en.wikipedia.org/wiki/Elevation_(view)) looking up one of the edges of the pyramid.

b         a
-----------                 x 
| .      .|                /|\     ^
|   .  .  |               / | \    |
|    x    |l=h*pi/2      /  |  \   h
|  .   .  |             /   |   \  |
|.       .|            / A) |    \ v
-----------            -----------
a         b            a    b    a

"A" is the angle between an edge and the base. We know that the (total length of the sides/height)=2π. This gives the length of each side. What is angle that each edge makes with the base (labelled "A" in the right hand diagram)? Hint - first determine tan(A). More hints [191] Here's the answer [192] .

The limestone casing stones were moved to the face of the pyramid as rectangular blocks, and the cut and polished in place. The builders used a jig to determine the angle of the face. Here's how it would have been done The Tura stone quarries (http://www.cheops-pyramide.ch/khufu-pyramid/stone-quarries.html#tura)

Now let's calculate the angle between the faces and the base. Once the angle between the edge and the base is determined, is angle between the base and the face fixed? (Can you make different pyramids all with the same angle between the edge and the base [193] ? Can you prove this [194] ?)

Here's the diagram of the pyramid again, this time with the elevation view rotated by 45°. "A" is the angle between a face and the base.

-----------                 x
| .      .|                / \     ^
|   .  .  |               /   \    |
|    x    |l=h*pi/2      /     \   h
|  .   .  |             /       \  |
|.       .|            / A)      \ v
-----------            -----------
a         b            a         b

As well as the length of the side and the height of the pyramid, we now also know the length of the diagonal across the base. How do we determine tan(A) (Hint: [195] )? Here's the diagram with the hint added [196] . Here's a much too complicated way of finding the length of the adjacent side (this was my first method, only read this if you have lots of time) [197] , but here's a much simpler way of doing it [198] . Here's the answer [199] .

The Egyptians didn't measure the slope (the tan() of the angle that the surface makes), but the inverse of the slope (which in English is sometimes called the inclination, a term that has many meanings, one of which is the angle of departure from the vertical). Instead of measuring the rise in height with a fixed distance (the slope), Egyptians measured the distance required to raise the height by a fixed amount. This is the same as the slope of a rod which is normal to the face. This the Egyptians called the seked (pronounced seqt; see Ancient Egytpian units of measurement http://en.wikipedia.org/wiki/Ancient_Egyptian_weights_and_measures).

Note
Also called the "seqt". See Thales (http://en.wikipedia.org/wiki/Thales) where Thales measures the height of the pyramids by measuring the length of the shadow at a time when his shadow was the same as his height. In this URL see a worked problem on the slope of the sides of a pyramid.

In current terminology the seked is cot()=1/tan()=tan(co-angle=90°-angle). A seked of 5 (a horizontal distance of 5 palms, per cubit of rise) is 54.46°, 5&1/4 seked (5 palms and 1 digit) is 53.13°, and 5&1/2 seked (5 palms and 2 digits) is 51.84°. It would seem that the face of the Great Pyramid has a seked of 5&1/2.

Ralph Greenberg explains in Pi and the Great Pyramid (http://www.math.washington.edu/~greenber/PiPyr.html) that just by good luck π≅22/7 and that a choice of inclination of 5&1/2 seked gives a slope of 28/22. Greenberg says that the origin of dividing cubits into 7 intervals is lost in the past, at least back to 150yrs earlier than the Great Pyramid to the era of Imhotep (http://en.wikipedia.org/wiki/Imhotep) who is considered to be the first engineer known by name.

Here we see that the ratio of 22/7 to π is out by 0.04%, the same error as the ratio (distance around the base)/height.

dennis:# echo "(22/7)/(4*a(1))" | bc -l	#(22/7)/pi
1.00040249943477068197

It's a little disappointing to find that with the precision of the other measurements of the Great Pyramid, that the Egyptians were using 22/7 for their calculations and not π.

11.4. Angles in other Pyramids

What is the relationship between the angle base-to-edge and the angle base-to-face? Assume that the length of one side of the base is l rather than h*π/2. Find the formulae for the two angles. Here's my answer [200] .

If tan(edge-to-base)=x, then tan(face-to-base)=x*sqrt(2) (the sqrt(2) is the ratio of the length of the hypoteneuse to the sides in a 45° right triangle). Since the two angles are different, for what angle(s) are they most different? With a flat pyramid, both angles will be nearly the same. With a very high pyramid, both angles will also be nearly the same. We can guess that the two angles will have the greatest difference somewhere in between (say with the angles about 45°). I decided to find this out for fun; It's not designed to illuminate any computing or trigonometric principle. Here's a graph with edge-to-base angle on the x-axis and face-to-base on the y-axis (code is here [201] ).

Figure 2. graph of the angles in a pyramid: red line x-axis: angle edge to base. y-axis: angle face-to-base. grey line - reference line where both angles are equal.


graph of the angles in a pyramid: red line x-axis: angle edge to base. y-axis: angle face-to-base.
grey line - reference line where both angles are equal.

The maximum difference can be found in a couple of equivalent ways

  • The greatest height difference between the red and grey line. This occurs (approx) with (x,y)=(40°40°) and (x,y)=(40°,50°), the difference being 10°.
  • The greatest horizontal difference between the red and grey line. This occurs (approx) with (x,y)=(40°50°) and (x,y)=(50°,50°), the difference again being 10° (we would hope that the difference is the same no matter which direction you measure it in).
  • Measure the greatest distance between the two lines, in the direction normal to the grey line, which has slope=1. This occurs between (x,y)=(45°,45°) on the grey line and (x,y)=(50°,50°) on the red line. This gives the maximum difference occuring for base-to-face angle=50°.

The ratio of the tan() should be sqrt(2).

pip:~# python
Python 2.4.4 (#2, Mar 30 2007, 16:26:42) 
[GCC 3.4.6] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> sqrt(2)
1.4142135623730951	#what we want
>>> tan(radians(40))
0.83909963117727993
>>> tan(radians(50))
1.19175359259421
>>> tan(radians(50))/tan(radians(40))
1.4202766254612063	#not quite correct
.			#several trials
.
>>> tan(radians(49.94))/tan(radians(40.06))
1.4142495433444027	#close enough

It would seem that the maximum difference occurs somewhere close to (x,y)=(40.06°,49.94°)

Its dimensions of the Transamerica Pyramid (from http://ecow.engr.wisc.edu/cgi-bin/get/cee/340/bank/11studentpro/transamericabuilding-2.doc) are 174'*174'*853'. The bottom 5 stories have vertical walls; presumably the pyramid starts at the 6th floor. Let's assume it's a pyramid. What is the angle edge-to-base, and face-to-base [202] ?

12. Elementary Probability

12.1. One Coin Toss

If you flip a coin many times, it will be heads half the time and tails half the time. The probability (==chance) of heads for any flip is 0.5 (pH=0.5), while the probability of tails for any flip is 0.5 (pT=0.5).

Note
pH+pT=1.0, i.e. you've accounted for all possible outcomes.

12.2. Two Coin Toss

If you flip two separate coins (or equivalently the same coin twice), and observe the outcomes you'll find the following.

Table 9. Two coin toss, results for 2nd toss

Result for 1st coin Result for 2nd coin
heads pH=0.5, pT=0.5
tails pH=0.5, pT=0.5

This result should be so obvious that you wonder why it's mentioned. The results demonstrate the outcomes of independant events (here coin tosses). The fact that the results of each coin toss are independant, allows us to construct this table.

Table 10. Outcomes from two coin toss

Result for 1st coin Result for 2nd coin Result for 2nd coin Result for 2nd coin
heads pH=0.5, pT=0.5 pH=0.5, pT=0.5 pH=0.5, pT=0.5
tails pH=0.5, pT=0.5 pH=0.5, pT=0.5 pH=0.5, pT=0.5
Footnotes:[203]


[1]

#! /usr/bin/python
# calculate_interest.py

annual_interest=1.0
initial_deposit=1.0
evaluations_per_year=1

balance=initial_deposit*(1+annual_interest/evaluations_per_year)

print "evaluations/year %10d balance is %2.10f" %(evaluations_per_year, balance)

# calculate_interest.py-------------------

[2]

#! /usr/bin/python
# calculate_interest.py

annual_interest=1.0
initial_deposit=1.0
evaluations_per_year=2

balance=initial_deposit*(1+annual_interest/evaluations_per_year)
balance*=(1+annual_interest/evaluations_per_year)

print "evaluations/year %10d balance is %2.10f" %(evaluations_per_year, balance)

# calculate_interest.py-------------------

[3]

#! /usr/bin/python
# calculate_interest.py

# calculate interest on $1 for 1yr, with interest = 100%/yr
# interest is calculated at fractions of a year, updating balance each time.

annual_interest=1.0
initial_deposit=1.0
evaluations_per_year=2

balance=initial_deposit
for x in range (1, evaluations_per_year+1):
	balance=balance*(1+annual_interest/evaluations_per_year)
	print "%d balance is %f" %(x, balance)

print "evaluations/year %10d balance is %2.10f" %(evaluations_per_year, balance)
# calculate_interest.py-------------------

[4]

#! /usr/bin/python
# calculate_interest.py

# calculate interest on $1 for 1yr, with interest = 100%/yr
# interest is calculated at fractions of a year, updating balance each time.

annual_interest=1.0
#evaluations_per_year=2

for evaluations_per_year in (1,2,10,100,1000,10000,100000,1000000,10000000,100000000,2000000000):
	initial_deposit=1.0
	balance=initial_deposit
	for x in xrange (1, evaluations_per_year+1):
		balance=balance*(1+annual_interest/evaluations_per_year)
		#print "%d balance is %f" %(x, balance)

	print "evaluations/year %10d balance is %2.10f" %(evaluations_per_year, balance)

# calculate_interest.py-------------------

[5] for n=53 (bigger than the 64-bit mantissa), then (1+1/2n) == 1.0. You'll get the result that e=1.

[6]

#remembering handy numbers
2^32=4G   (approximately)

so 
10^9=2^30 (approximately)

You'd need 30 iterations.

[7]

Assuming a 52-bit mantissa, n=252. You will then be calculating e=(1+1/n)n, where n=252. You can only have 52 iterations (squarings).

[8]

No. The heights for each interval are different; each height has to be calculated independantly.

[9]

#! /usr/bin/python
# calculate_interest_logarithmic.py

# (C) Warren Buffett 2009, licensed under GPLv3
# calculate interest on $1 for 1yr, with interest = 100%/yr
# calculate interest at fractions of a year, updating balance each time.

initial_deposit=1.0
annual_interest=1.0

iterations=1
evaluations_per_year=2**iterations
print "evaluations/year %10d" %(evaluations_per_year)
x=1+annual_interest/evaluations_per_year
iteration = 1
print "iteration %3d balance=%2.20f" %(iteration, x * initial_deposit)

# calculate_interest.py-------------------

[10]

[11]

[12]

#! /usr/bin/python
# e_by_factorial.py

#initialise numbers
e=1.0
factorial_n=1.0

#main()

factorial_n /= 1	#what is "/=" doing?
e+=factorial_n

print e

# e_by_factorial.py --------------------

[13]

#! /usr/bin/python
# e_by_factorial.py

#initialise numbers
e=1.0
factorial_n=1.0
num_iterations=20

#main()

for x in range (1,num_iterations):
	factorial_n /= x
	e+=factorial_n
	print "%2.30f" %e

# e_by_factorial.py --------------------

[14]

A 64 bit real has a 52 bit mantissa, which only supports 15 or 16 significant decimal digits. Beyond that, the numbers being added by the series, are too small for the precision of a 64 bit real, and appear to be 0.0. See machine's epsilon for the smallest number than can be differentiated from 1.0 with a 64-bit real (the number is 1.11022302463e-16).

[15]

256/2=128. A triangle has half the area of the corresponding rectangle/square. You can see this by making a copy of the triangle, rotating it 180° in the plane of the paper and joining it along the hypoteneuse to the first triangle, when you'll make a square (or rectangle). If you agree with this answer, you've forgotten the fencepost error. This answer is close, but not exact.

[16]

The area of the rectangle is 16*17=272. The number of operations is half the area of the rectangle, i.e. 136.

[17]

The sum of the elements is half the area of the rectangle. The base (width) of the rectangle is the number of elements. The height is the sum of the value for the first element + the value of the last element. The sum then is

sum = n(first + last)/2

[18]

The area of the object shown with "*" is 16*(16+3)/2=152.

[19]

The number of bits in the mantissa that could be wrong is between 7 (would need 128 operations that were rounded) and 8 bits (256 operations that were rounded). The exact calculation of the number of operations doesn't give results that any different to any of the back of the envelope calculations - either the number 256=16*16 or the fencepost error value of 128. All give 7-8 bits of error (2-3 decimal digits).

[20]

Take the last digit of each number.

1+2..+9=45

You need to do this addition 10 times (for the numbers 0x, 1x..9x) giving a sum of 450. Then you take the first digit of each number

1+2..+9=45

The first digit of each number represents a 10, so multiply this result by 10. You have to add the first digit 10 times, giving the sum of the first digits as 4500. Add your two numbers

450+4500=4950

then add 100

4950+100=5050

[21]

100

[22]

101*100

[23]

101*100/2=5050 (You halve the previous result because the arithmetic progression was added in twice)

[24]

Napoleon has nothing to do with the size of the earth, just the size of the units that the earth is measured in.

Napoleon mandated that France adopt the metric system. But first they had to devise a metric system. The m was defined as the 10,000th part of the length of the meridian, from the pole to the equator through Paris. This defines the circumference of the earth as 40,000km. Unfortunately the initial estimates weren't accurate, and as well the earth isn't spherical (it's a geoid, an oblate sphere, i.e. it's flattened at the poles due to the earth's rotation). The actual circumference is 40,075km at the equator and 40,008km through the poles. However 40,000km is close enough for most purposes.

[25]

Number of layers                        = 146/0.5 = 292
Number of casing stones in bottom layer = 230/1   = 230
Number of casing stones in top layer    = 1

casing stones on one face  = n(first + last)/2
	                   = 292(230+1)/2
	                   = 33726

casing stones on all faces = 134904

[26]

44 (remember the fencepost problem)

[27]

Number of layers                  = 22
Number of windows in bottom layer = 58
Number of windows in top layer    = 8

windows on one face               = n(first + last)/2
	                          = 22*(58+8)/2
	                          =
pip:~# echo "22*(58+8)/2" | bc -l
726

Number in 4 faces
pip:~# echo "22*(58+8)/2*4" | bc -l
2904

[28] Computer joke: but first, some background info

Not only can functions return a number, but main() in C (and now most languages) also returns a number to the operating system, when the program exits, allowing the operating system to handle execution failures. The number returned on success is 0. The numbers -128..127 are returned for different types of failures.

The Roman Empire lasted for about 400 years (computer programmers have to know this sort of stuff) (see Roman Empire http://en.wikipedia.org/wiki/Roman_Empire), and there is much debate about why it collapsed.

OK the joke: the Roman Empire collapsed because it didn't have a "0" to signal the successful completion of their C programs.

[29]

(1,1)

[30]

In quick succession the following occured

  • Kepler in 1605 discovered the laws that governed the motions of planets Kepler's Laws of Planetary Motion (http://en.wikipedia.org/wiki/Kepler's_laws_of_planetary_motion) (the planets move in ellipses about the sun at one of the focii). (Previously planets were called heavenly bodies, since they were associated with God. The words for sky, heaven and god were all related. e.g. The name "Jupiter" comes from Ju (dieu), which originally meant sky, and Pater (father). Jupiter was the "sky father".)

  • Galileo (1609) built a telescope (based on previous work by a Dutch spectacle maker Lippershey) allowing the exploration of the night sky.

  • Isaac Newton (http://en.wikipedia.org/wiki/Isaac_Newton) and Leibnitz co-discovered calculus (late 1600s). Newton determined that gravity varied as the inverse square of the distance and that an inverse square field would produce the elliptical orbits. (It didn't take long to figure out that the inverse square relationship was dependant on a 3-D universe. Although initially the math appeared difficult, the fact that the inverse square appeared in lots of places, gave people plenty of practice at using it and eventually everyone got used to it.)

The sudden arrival of a host of new moving objects: Uranus, Neptune and the asteroids, meant that people could predict where they would be at future times. Previously you had to wait for word from God on this, but now with a table of logarithms, you could bypass this requirement.

The discovery of Uranus by Herschel, was just luck (and Herschel being the first kid on the block with a telescope). The discovery of Neptune came from tracking perturbations in Uranus's orbit (see Mathematical discovery of planets http://www-groups.dcs.st-and.ac.uk/~history/HistTopics/Neptune_and_Pluto.html). Uranus had been tracked for almost 1 complete orbit, since its discovery by Herschel in 1781, when by 1845, separately Adams (in England) and Le Verrier (in France) (http://en.wikipedia.org/wiki/Urbain_Le_Verrier) determined the same location for a perturbing body. Neither Adams nor Le Verrier knew of each other's work. The results of Le Verrier's calculations were announced before the Frence Academy, in the hopes of someone finding the new planet. Adams calculations were known by some, but not publically circulated till after the discovery of Neptune. Remarkably, no-one acted on the predictions, even Airy, the Astronomer Royal in England, who was in possession of both sets of calculations showing identical results. I have not been able to find the amount of time that Le Verrier spent with tables of logarithms, but it would appear Adams (http://en.wikipedia.org/wiki/John_Couch_Adams) spend from 1843-5 on it.

You may wonder at such universal inaction on the part of observers, in the face of overwhelming evidence of a great discovery waiting to be made, but you must understand that no-one had predicted the discovery of a new planet before, and you quite justifiably didn't want to go to all the trouble of wasting an evening looking through your telescope, when you had more productive things to do. Clearly the prudent thing to do was to first let other people make a chain of successful predictions and discoveries, before expending any of your valuable time on such nonsense yourself. Eventually Galle (France) looked and after 30 mins of searching, found the planet at magnitude 8 (easily within reach of telescopes of the era), exactly where it had been predicted. On looking back through records, it was found that many people had observed Neptune, including Galileo in 1613, 200 yrs earlier, and all had observed it to move, but had not recognised it as a planet.

People weren't so reticent the next time and based on discrepencies in the orbit of both Uranus and Neptune, in January 1929 Clyde Tombaugh found Pluto. This was hailed as another success for rational thought, and was touted as such at least till my childhood (1950s). There was a minor problem: Pluto was only 1/300th the mass of the expected object. It wasn't till recently (1990's or so) that modern computers, recomputed the original data, to find that the descrepencies in the orbits of Uranus and Neptune were due to rounding errors in the original calculations. There never had been a perturbing planet. There just happens to be a lot of dim small stuff out there, which no-one knew at the time, and if you look long enough you'll find some of it. Tombaugh had not discovered Pluto by rational means; he was lucky and his extensive search was rewarded by success. This aspect of the discovery was kept quiet (hard work is not as dramatic as discovery by inspiration).

Another aspect of the discovery was kept quiet: Pluto wasn't a planet. Pluto doesn't have any of the properties of the other planets: its orbit is inclined to the plane of the ecliptic (the plane in which the planets orbit the sun) and is highly elliptical, with Pluto's orbit going inside that of Neptune for some part of its orbit. There 40 other Pluto like objects discovered so far, in inclined and highly elliptical orbits, some of which are larger or brighter (e.g. Xena) than Pluto. Pluto clearly was formed by a different mechanism to the 8 planets, all of which have essentially circular orbits and all of which orbit in the same plane. It became clear that Pluto has more affinity to these objects than it does to the planets. In 2006 Pluto was demoted from planethood and the new classification of dwarf planets was created for these objects.

While it had now been clearly established that you could discover planets by mathematics, the greater lesson that mathematics would predict other great discoveries, was still difficult to grasp. In the middle of the 20th century, the great problem in cosmology was whether the universe was in a steady state and had existed forever at its current temperature, or whether it had begun as a fireball at billions of degrees (the "big bang") 13.7Gya and had been expanding (and cooling) ever since. You'd think it would be easy to differentiate these two situations, but the best minds spent half of the century on it without success. George Gamow (http://en.wikipedia.org/wiki/George_Gamow) in 1948 provided the clue. He calculated that if the universe had started off as a fireball, the temperature, following expansion (and subsequent cooling) to its current size, would be 5°K and would be observable as an isotropic background of microwave radiation. Gamow's prediction allowed anyone (even high school students) to calculate the size of antenna you would need to detect this background radiation, knowing the noise temperature of your microwave receiver. The microwave equipment of the era was too noisy to contemplate looking for the signal, but microwave electronics had only just been invented (WWII, for radar) and you could expect rapid progress in the development of microwave receivers. You could plan for the day when low noise amplifiers would be available. A back of the envelope calculation would let you know if you were going to have to wait 1,10 or 100yrs. You also knew how much longer you'd have to wait if Gamow's calculation was out by a factor of (say) 10. (It turns out that the improvements needed to find the radiation were small. The numbers are all well known by people in the field, but I couldn't find them with google.) Remarkably no-one looked for the radiation or even planned to. With the mechanism to solve the cosmic problem of the century in hand, you'd think everyone would at least keep the idea in the back of their minds (or go spend the intervening time improving microwave equipment). At least you'd keep track of other people's developments in microwave amplifiers.

As it turned out the microwave version of the low noise parametric amplifier (http://en.wikipedia.org/wiki/Parametric_oscillator) was in production shortly thereafter for satellite communications and was being built at home by ham radio operators by the 1960s. The maser (noise temperature 40°K in 1958, see http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel6/22/24842/01124540.pdf?arnumber=1124540) was being used at the front end of satellite ground stations. Meanwhile huge antennas were being built for radioastronomy and for satellite communication.

In 1962 Telstar (http://en.wikipedia.org/wiki/Telstar) was launched. It relayed phone signals between North America and Europe. Telstar was a big deal to me growing up in Australia. It was the first satellite that wasn't part of the cold war, or designed for bragging rights in the war between the two thugs on the block, USA and Russia, competing to win the hearts and minds of the world's population. Telstar's job as far as I knew was to relay phone calls between members of a family separated by a large ocean (travel was slower and more expensive back then). (It was also used to send TV images of sports programs and data like stock prices, but I didn't know that.) Unfortunately Telstar didn't last long; it was fried by radiation from atomic testing in space, by both the American and Russian bombs. Telstar had an energy budget of only 14W and required huge antennas on the ground to receive the signal; in Andover Maine, Goonhilly Downs Satellite Earth Station (http://en.wikipedia.org/wiki/Goonhilly_Satellite_Earth_Station) (go look at the photos of Arthur) and the quietest microwave receivers that money could buy. (Nowadays geostationary satellites are the size and weight of a bus, have 10kW of power and can deliver TV signals into homes which have an antenna the size of a dinner plate.) The launch of Telestar and photos of the ground station antennas were blazed across the TV for months. There was even a tune by Joe Meek, played by the Tornados about Telstar, (http://en.wikipedia.org/wiki/Testar_(song)) which made it to #1 in both Britain and the US, and later a song ("Magic Star"). It's hard to imagine how any of the people looking for Gamow's background radiation, on seeing these satellite earth station antennas every night on TV in their living room, didn't think "I really ought to get time on one of these antennas".

In 1965, several generations of microwave equipment after Gamow's prediction, a pair of engineers Penzias and Wilson (http://en.wikipedia.org/wiki/Discovery_of_cosmic_microwave_background_radiation) at Bell Labs in Holmdel, NJ, did thorough and exhaustive calibrations on the horn antenna (http://en.wikipedia.org/wiki/Horn_Antenna) left over from the 1960 Echo Satellite project (http://en.wikipedia.org/wiki/Echo_satellite). The antenna was no longer needed and Bell was hoping trying to find some new use for it (Bell's intention was to save money rather than make a great discovery). Whether Penzias and Wilson were being sent off on what management thought a dead end project, I have not been able to determine. Penzias and Wilson intended to use the antenna for radioastronomy and satellite communication experiments. Penzias and Wilson found that the noise temperature of their setup was 15°K, but they could only account for 12.3°K by dissambling the setup and measuring each part separately. Most people at that stage would have said "that's near enough" and moved on, but Penzias and Wilson were top engineers and apparently not bothered by management, spent months trying to account for the excess noise.

The weak link in their calculations was that the noise temperature of the antenna could not be measured; it had to be calculated. While it's easy to model a perfectly smooth, accurate, infinite sized antenna, a real, finite, irregular (joins, bumps from screws) antenna with an inaccurate surface is difficult to model. The antenna surface was covered in white dielectic material (pigeon droppings) (see Penzias http://en.wikipedia.org/wiki/Arno_Allan_Penzias), which was removed (and the pigeons shot), without effect on the noise. The calculated antenna noise was close to the amount of excess noise. A small miscalculation about the antenna and there would be no excess noise (just as there was no planet perturbing Uranus and Neptune). Not having any other ideas, they reluctantly decided that the noise must be coming from the sky. It wasn't coming from anything localised, like the Milky Way, or nearby New York City; instead it came uniformly from all directions. Penzias and Wilson had accidentally discovered the cosmic microwave background (CMB) radiation, at 2.7°K. (The amount of noise is now agreed to be 4°K. Presumably Penzias and Wilson had overestimated the amount of noise due to the antenna by 1.3°K.)

Since Penzias and Wilson weren't looking for the CMB, they had to ask around to confirm what they were listening to. The academic world was astonished to find that the microwave background was strong enough that it was found by people who weren't even looking for it. The discovery was quickly confirmed by the many sites in the world already capable of detecting the noise, similtaneously demolishing the Steady State theory, confirming the Big Bang theory and winning a Nobel Prize for Penzias and Wilson. Some of the esteemed researchers in the field, who'd spent the last 50yrs trying to figure out the problem, pooh-poohed the discovery as trivial and certainly unworthy of a Nobel Prize. (Talk about a bunch of sore losers.) In particular, Penzias and Wilson were regarded as just a pair of technicians who'd only read out the dials of the equipment they'd been given. They were actually engineers, which made it worse. Academics are used to writing the grants, then telling engineers to build and run the equipment, do the experiments and hand over the results, so that the academic can write the papers and get the glory. (For a description of petty, venal and arrogant academics terrified of their technicians beating them to a discovery, it's hard to beat "The Hubble Wars: Astrophysics meets Astropolitics in the Two-Billion-Dollar Struggle over the Hubble Space Telescope" Eric J. Chaisson, 1994 Harper Collins, ISBN 0-06-017114-6.)

The detractors hadn't learned the lesson, that if you're going to use a piece of equipment, you have to know what it does, before you use it. Plenty of people had had the opportunity to search for the radiation themselves, but hadn't; plenty of people had heard the radiation, but had ignored it; plenty of people had had not properly calibrated their equipment, and didn't realise the radiation was there. The detractors were, in fact, loudly proclaiming to the world their lack of understanding of what's required for a great discovery. What greater discovery can there be, than seeing something when you've been told there is nothing, while all those around you, who have been told what to look for, see nothing (the reverse of the Emporer's new clothes).

There were however lots of people who recognised the great discovery. I remember being a first year undergraduate at the time. I heard about it when the Physics lecturer burst through the door and instead of giving the lecture for the day, told us about the discovery, drawing on the blackboard, a graph of the energy of the microwave background radiation as a function of frequency, and the points corresponding to the energy found by the multiple sites. It was the only time in my student life that something, from the outside world, was important enough to bump a scheduled lecture. Unfortunately the significance of the matter was largely lost on me at the time. All I knew was that something important had happened. It wasn't till sometime later that I understood where the radiation came from and why it should have that temperature. While it is often said of people of my generation, that we remember where we were when we first heard about the assassination of Jack Kennedy, I remember where I was when I heard about Penzias and Wilson's discovery of the cosmic background radiation.

[31]

#! /usr/bin/python
#compound_interest.py

principal=0
interest_rate=0.05
annual_deposit=10000
age_start=18
age_retirement=65

principal+=annual_deposit
interest=principal*interest_rate
principal+=interest

print "principal %10.2f" %principal

[32]

#! /usr/bin/python
#compound_interest.py

principal=0
interest_rate=0.05
annual_deposit=10000
age_start=18
age_retirement=65

for age in range(age_start+1,age_retirement+1):
	principal+=annual_deposit
	interest=principal*interest_rate
	principal+=interest

	print "age %2d principal %10.2f" %(age, principal)

# compound_interest.py------------------------

[33]

$100k

[34]

$370,000, $1,840,000

[35]

Till you're 30.

[36]

interest/month=principal*annual_interest/12

# echo "23750*6/(100*12.0)" | bc -l
1187.50

[37]

payment = interest + pay-off on principal

1187.50+100.00=1287.50

[38]

less: The 2nd month's payment is $6.44 less than the 1st month's payment, because your principal was reduced by the $100 payment in the first month.

1st month:
principal at start          =237500.00
interest on principal at 6% =           1187.00
principal                   =            100.00
1st payment (end month)     =           1287.50

2nd month:
principal at start 2nd month=236212.50 
interest on principal at 6% =           1181.06
payment of $100.00 principal=            100.00
2nd payment (end month)     =           1281.06

[39]

The monthly payment is the sum of the interest that's paid in the first month and the initial principal payment.

[40]

The principal paid out each month is the difference between the monthly payment and the interest calculated for that period.

[41]

The number of payment periods * the monthly payment.

[42]

#!/usr/bin/python

#mortgage_calculator.py
#Abraham Levitt (C) 2008, licensed under GPL v3

initial_principal = 250000.0    #initial value, will decrease with time
principal_remaining = initial_principal 
principal_payment = 0.0

interest  = 0.06                #annual rate
interest_payment = 0.0		#monthly interest
total_interest_paid = 0.0	#just to keep track
	                        #(in the US, if you itemise deductions, 
	                        #the interest on a mortgage for your primary residence is tax deductable)

initial_principal_payment = 100.00        #This is the initial value of the monthly principal payment
time_period = 0

#monthly payment is fixed throughout mortgage
monthly_payment = initial_principal*interest/12.0 + initial_principal_payment

#print column headers
print " time    princ. remaining     monthly_payment   principal payment    interest payment      total int paid"

#print balances at the beginning of the mortgage
print "%5d %19.2f %19.2f %19.2f %19.2f %19.2f" %(time_period, principal_remaining, monthly_payment, principal_payment, interest_payment, total_interest_paid)


#calculate for first month
interest_payment = principal_remaining*interest /12.0
total_interest_paid += interest_payment
principal_payment = monthly_payment - interest_payment
principal_remaining -= principal_payment
time_period += 1

print "%5d %19.2f %19.2f %19.2f %19.2f %19.2f" %(time_period, principal_remaining, monthly_payment, initial_principal_payment, interest_payment, total_interest_paid)

# mortgage_calculation.py ------------------ 

[43]

#!/usr/bin/python

#mortgage_calculator.py
#Abraham Levitt (C) 2008, licensed under GPL v3

initial_principal = 250000.0    #initial value, will decrease with time
principal_remaining = initial_principal 
principal_payment = 0.0

interest  = 0.06                #annual rate
interest_payment = 0.0		#monthly interest
total_interest_paid = 0.0	#just to keep track
	                        #(in the US, if you itemise deductions, 
	                        #the interest on a mortgage for your primary residence is tax deductable)

#initial_principal_payment = 250.00        #This is the initial value of the monthly principal payment
initial_principal_payment = 248.876       #This is the initial value of the monthly principal payment
time_period = 0

#monthly payment is fixed throughout mortgage
monthly_payment = initial_principal*interest/12.0 + initial_principal_payment

#results:
#6%, 250$ initial payment, 1500$ monthly payment, 288k$ interest

#print variables
print " initial_principal_payment %19.2f annual_interest %2.4f" %(initial_principal_payment, interest)
#print column headers
print " time    princ. remaining     monthly_payment   principal payment    interest payment      total int paid       total payment"

#print balances at the beginning of the mortgage
print "%5d %19.2f %19.2f %19.2f %19.2f %19.2f %19.2f" %(time_period, principal_remaining, monthly_payment, principal_payment, interest_payment, total_interest_paid, total_interest_paid + initial_principal - principal_remaining)


#calculate for all months
while (principal_remaining >= 0.0):
	interest_payment = principal_remaining*interest /12.0
	total_interest_paid += interest_payment
	principal_payment = monthly_payment - interest_payment
	principal_remaining -= principal_payment
	time_period += 1
	if (time_period %12 == 0):
		print "%5d %19.2f %19.2f %19.2f %19.2f %19.2f %19.2f" %(time_period, principal_remaining, monthly_payment, principal_payment, interest_payment, total_interest_paid,  total_interest_paid + initial_principal - principal_remaining)



#print stats 
print "interest %2.4f, monthly_payment %5.2f, initial_principal_payment %5.2f, ratio (total cost)/(house price) %2.4f" %(interest, monthly_payment, initial_principal_payment, (total_interest_paid + initial_principal - principal_remaining)/initial_principal)

# mortgage_calculation_2.py ------------------ 

[44]

540k$=250k$ principal + 290k$ interest. The mortgage doubles the price of the house (approximately).

[45]

About 20yrs. Most of your initial payments are interest. In the last 10yrs most of your payments are principal.

[46]

You own about 20k$ of your 250k$ house. You've paid 86k$ of interest in the same time.

[47]

at 6% interest, after 6yrs: equity = 250-228k$=22k$

at 6% interest, after 6yrs: interest = 86k$

[48]

0.5%*160k$=$800/yr. $800 is a nice amount of money to save a year.

Note
As you pay off the loan and your principal decreases, your savings will decrease. However it will be a while before you make a significant dent in the principal.

[49]

$3200/$800=4yrs.

[50]

$3200/$30≅9yrs

[51]

dennis:# echo "3*1.0525^25" | bc -l
10.78136793454438844136

[52]

dennis:# echo "3*1.04^25" | bc -l
7.99750899446225997912

[53]

If you halve the size of the transistor, then you can fit 4 times as many transistors into the same area. The number of transistors scales with O(n2).

[54]

#! /usr/bin/python

#moores_law_pi.py
#Gordon Moore (C) 1965
#calculates the number of years needed to calculate PI, 
#if the speed of the computers increases with Moore's Law

year = 10**7.5  		#secs
doubling_time = year
time = 0
iterations_required = 10**100   #iterations
iterations_done = 0     	#iterations
initial_computer_speed = 10**9  #iterations/sec 
				#This number is actually clock speed. 
				#iterations/sec will be down by 10- 100 fold, 
				#assuming 10-100 clock cycles/iteration. 
				#This is close enough for the answer here.
computer_speed = 0  		#iterations/sec 
	 
#setup variables for the first year

computer_speed = initial_computer_speed
time = year

#calculate the iterations for the year
iterations_done = computer_speed * year

#print the iterations done
print "elapsed time (yr) %5.0f, iterations done %e" %(time/year, iterations_done)

# moores_law_pi.py

[55]

#! /usr/bin/python

#moores_law_pi.py
#Gordon Moore (C) 1965
#calculates the number of years needed to calculate PI, 
#if the speed of the computers increases with Moore's Law

year = 10**7.5  		#secs
doubling_time = year
time = 0
iterations_required = 10**100   #iterations
iterations_done = 0     	#iterations
initial_computer_speed = 10**9  #iterations/sec 
				#This number is actually clock speed. 
				#iterations/sec will be down by 10- 100 fold, 
				#assuming 10-100 clock cycles/iteration. 
				#This is close enough for the answer here.
computer_speed = 0  		#iterations/sec 
	 
#setup variables for the first year

computer_speed = initial_computer_speed

while (iterations_done < iterations_required):

	time += year
	#calculate the iterations for the year
	iterations_done += computer_speed * year

	#print the iterations done
	print "elapsed time (yr) %5.0f, iterations done %e" %(time/year, iterations_done)

	computer_speed *= 2

# moores_law_pi.py ------------------------------------

[56]

at the end of the 277th year (the speed doubles every year, the first 277 years did half the job, the 278th year did the last half of the job).

[57]

1 yr. (It would have been cheaper too, you wouldn't have had to buy 278 years worth of computers.)

[58]

255 = 256-1

[59]

The series is 1+2+4+..231=231+230+...+1. In binary, this is 32 1's. In hexadecimal it's ffffffffh. The sum (the number represented by 32 bits, all 1) is 232-1≅4G

[60]

11110

[61]

2

[62]

if 
a = b
c = d

then
c-a = d-b

[63]

Sum=1*(28-1)/(2-1)=255

[64]

Sum=1*(1-0.1)/(1-0.1) = 1/0.9 = 1.111

[65]

Sum=1*(1-0.2)/(1-0.2) = 1/0.8 = 1.25

[66]

Sum=a*(1-r^n)/(1-r)

for n=infinity, r<1
r^n=0

Thus 
Sum=a/(1-r)

since r!=1, this is a finite number.

[67]

1+2+4+...263=264-1 (this just happens to be the largest unsigned integer that can be represented on a 64-bit computer)

[68]

264-1 secs

(the age of the universe is 13.8Gyr)
pip:# echo "2^64/(60*60*24*365*13.8*10^9)" | bc -l
42.38713169239652409208 times the age of the universe.

[69]

64 secs (there may be technical difficulties pouring the last few piles in 1sec).

[71]

dennis:# echo "2^64*1.0/(60000*750)" | bc -l
409927646082.43448035555555555555


let's turn this into more usable units

dennis:# echo "2^64*1.0/(60000*750*10^12)" | bc -l
.40992764608243448035	#Tm^3 (Tera cubic metres)
=.41	#Tm^3 (Tera cubic metres)

[72]

density of rice≅0.75ton/m3. Weight of rice = 0.3 Ttons

[73]

tan()

[74]

>>> tan(radians(26))
0.48773258856586144
>>> tan(radians(27))
0.50952544949442879
>>> 

[75]


volume=(4pi/3)*h^3
      =0.41 Tm^3

thus
h^3=0.41*(3.0/4*pi)Tm^3

>>> 0.41*(3.0/4*pi)
0.96603974097886136	

Let's take the cuberoot of 0.966 as 1.0

The cuberoot of 10^12 is 10^4

The height then is
h=1.0*10^4m 
 =10km

[76]

10km

[77]

10km. We have a pile of rice the size of Mt Everest.

[78]

20km

[79]

from Rice (http://unctad.org/infocomm/anglais/rice/market.htm), the world's production of rice in 2000 was 0.6G tons/year. The reward was 0.3 Ttons. It would take 0.3*1012/0.6*109≅500yrs for the world today to produce the amount of rice needed for the reward. Back in the time of this story (I would guess about 1000yrs ago), the world's production of rice would have been less. How much less? There aren't good records of rice production, but a lot of attention has been put into estimating the world's population. As a first approximation, we'll assume that rice production and world population are closely linked. The History World's Population (http://www.xs4all.nl/~pduinker/Problemen/Wereldbevolking/index_eng.html), itself an exponential growth, was about 1/1000 th the current level. The world's rice production then would have been 1/1000th of today's level. The Grand Vizier's reward represented 0.5Myr of the world's rice production at the time. Compare this time to the amount of time since humans diverged from the apes (about 0.1-1Myr).

[80]

sum = a(r^(n+1)-1)/(r-1)

a = 10^16.5, r=2
sum = 10^16.5*(2^(n+1)-1)

[81]

10^16.5*(2^(n+1)-1) = 10^100
2^(n+1)-1=10^83.5

ignore the -1 (it's small compared to 10^83.5).

you now want to find "n" when

2^(n+1)=10^83.5

try 2^(n+1)/10^83.5 for a few values of n

pip:class_code# python
Python 2.4.4 (#2, Mar 30 2007, 16:26:42) 
[GCC 3.4.6] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 2**278/10**83.5
1.5358146097473691

this gives n+1=278
n = 277

[82]

No, but lots of really neat capital equipment, coffee makers, iPods (all of which will rust, wear out, break, or become obsolete and have no residual value and houses, which will probably increase in value) was bought. Where the 9M$ is used to buy equipment which has no residual value, the money will be repaid by hard work by the employees at the various businesses.

[83]

Yes, 9M$ - out of thin air.

[84]

Ben Franklin

[85]

Fixed rate mortgages are currently about 6%. An ARM starts at a lower rate (about 4%), a great advantage over a fixed rate mortgage, to suck people in. However after about 2 yrs, the rate rises, eventually reaching about 10%, a level which few people can handle.The sub-prime people were promised that they would then qualify for a fixed rate mortgage. Whether this was a real promise at the time I don't know, but by the time the interest rate ramp hit, the bubble had broken and the mortgagees were left with negative equity in their houses (their houses were worth less than they'd committed to pay).

[86]

700G$/300M=2300$

[87]

$0

[88]

$0

[89]

No way in hell.

[90]

exponential. Each machine infects n others, those n machines go on to infect n2 others, eventually infecting nn machines.

[91]

The ice changes the aerofoil of the wings reducing lift so the plane can't fly, at the same time increasing the weight of the plane. An iced-up plane can't fly. This isn't a problem for modern aviation as today's planes can fly above the clouds.

[92]

An occupant in a plane which is turning, unless they have an external reference e.g. the horizon or the modern gyroscopic Artificial Horizon (http://en.wikipedia.org/wiki/Attitude_indicator) can't tell that they are tilted over. The tipping of the wings to one direction, drives the occupant directly into the seat. The occupant has no sideways forces and in a bumpy ride can't tell that there's an extra 5% weight pressing into the seat. As a result a blinded pilot has no perception of the horizontal.

I'm not a pilot, but I can't imagine how Nungesser expected to make it across the Atlantic on a moonless night. This inability to vertically orient in a plance is a possible explanation for the death of John Kennedy, (http://www.airlinesafety.com/editorials/JFKJrCrash.htm), (the son of President Kennedy) the pilot of a plane which crashed into the sea on a night approach to Martha's Vineyard (see the section "Why did Kennedy Crash?").

[93]

The pilot only knows his speed relative to the air. To tell groundspeed, he also needs to know the speed and direction of the air relative to the ground (or ocean).

Sailors, whose lives depend on understanding the wind and sea, have a the Beaufort Scale to determine windspeed (for landlubbers, there's also a scale for land). When I was in Cubs (US==cub scouts) (in the '50s), the Cub's den was decorated with rigging diagrams of ships, showing the names of the ropes and the sails, a board covered in the knots sailors used (more than we were every asked to know), and most importantly a Beaufort scale. While we all knew that we'd be unlikely to ever sail on one of these ships, they were an important part of the founding of Australia, and we knew that understanding these diagrams would help us understand the lives of people who made our country. You knew that lives were in peril at high Beaufort numbers. Have a look at the wiki photo for Beaufort 12. Few ships can handle more than a hour of that without breaking their backs. The wreck of the Edmund Fitzgerald (http://en.wikipedia.org/wiki/SS_Edmund_Fitzgerald), occured at Beaufort 10. It was originally thought that the boat had snapped in half, but it now seems likely that the hatches lost their watertightness, or the boat had earlier hit a shallow shoal, which wasn't noticed in the rough seas, causing damage to the hull.

[94]

in_air*(1.0-1.0/MTBF)

[95]

in_air*(1.0-interval/MTBF)

[96]

#! /usr/bin/python

#lindbergh_one_engine.py
#
#Charles Lindbergh (C) 2008, released under GLP v3.
#calculates the probability that Lindbergh's one engine plane 
#will still be flying after a certain number of hours.

#these should be reals otherwise will get integer arithmetic.
MTBF=200.0
in_air=1.0
time=0.0
interval=1.0	#sample at 1hr intervals

in_air *= (1.0-interval/MTBF)
time += interval
print "time %2d in_air %2.5f" %(time, in_air)

# lindbergh_one_engine.py ------------------------------

[97]

#! /usr/bin/python

#lindbergh_one_engine_2.py
#
#Charles Lindbergh (C) 2008, released under GLP v3.
#calculates the probability that Lindbergh's one engine plane 
#will still be flying after a certain number of hours.

#these should be reals otherwise will get integer arithmetic.
MTBF=200.0
in_air=1.0
time=0.0
fuel_time=38
interval=1.0	#sample at 1hr intervals

#print header
print "time in_air " 

for time in range(0,fuel_time+1): 
	print "%4d  %5.3f" %(time, in_air)
	in_air *= (1.0-interval/MTBF)

# lindbergh_one_engine_2.py ------------------------------

[98]

flying for 33.5hrs, he had an 84% chance of making it.

[99]

#! /usr/bin/python

#lindbergh_one_engine_3.py
#
#Charles Lindbergh (C) 2008, released under GLP v3.
#calculates the probability that Lindbergh's one engine plane 
#will still be flying after a certain number of hours.

#These are reals.
#Don't use integers or will get integer arithmetic.

MTBF=200.0
fuel_time=38.0

print "time in_air" 

interval=1.0	#update calculations at 1hr intervals
in_air  =1.0	#initial probability of engine running
time    =0.0	#time at start of flight

while (time <= fuel_time):
	#at start of an interval, print parameters
	print "%5.3f  %5.3f" %(time, in_air)

	#at end interval, update
	time += interval
	in_air *= (1.0-interval/MTBF)

# lindbergh_one_engine_3.py ------------------------------

[100]

The units of the y axis is probability, a number (with value between 0.0 and 1.0). The units of the x-axis is hours. The units of the area under the graph is number*hours=hours.

[101]

The only parameter measured in hours that's been input to the graph is the MTBF=200hrs. The area under the graph must bear some simple relationship to the MTBF, e.g. it could be exactly the MTBF or e*MTBF.

[102]

#! /usr/bin/python

#lindbergh_one_engine_4.py
#
#Charles Lindbergh (C) 2008, released under GLP v3.
#calculates the probability that Lindbergh's one engine plane 
#will still be flying after a certain number of hours.

#These are reals.
#Don't use integers or will get integer arithmetic.

MTBF=200.0
fuel_time=2500.0

print "time in_air" 

interval=1.0	#update calculations at 1hr intervals
in_air  =1.0	#initial probability of engine running
time    =0.0	#time at start of flight
area	=0.0

while (time <= fuel_time):
	#at start of an interval, print parameters
	area += in_air*interval
	if (time%100 <= interval/2.0):	#why don't we do (time%100==0)	as we've always done?
		print "%5.3f  %5.3f  %5.3f" %(time, in_air, area)

	#at end interval, update
	time += interval
	in_air *= (1.0-interval/MTBF)

# lindbergh_one_engine_4.py ------------------------------

[103]

The probability of the engine still working after a certain time is a continuous function. We've approximated it by a discontinuous function - a set of steps. Are the steps above or below the continuous function? The continuous function for the first hour goes from 1.0 to 0.995. The step function is 1.0 for the first hour. The step function then is an upper bound.

[104]

From our work on determining the value of π, the lower bound for a monotonic function is (upper bound - the first slice). The first slice has an area of 1hr).

[105]

It's between 199 and 200.

[106]

#! /usr/bin/python

#lindbergh_two_engines.py
#
#Charles Lindbergh (C) 2008, released under GLP v3.
#calculates the probability that Lindbergh's two engine plane 
#will still be flying after a certain number of hours.

MTBF=100
fuel_time=38
in_air=1.0
#time=0
print "time in_air " 

for time in range(0,fuel_time+1): 
	print "%4d  %5.3f" %(time, in_air)
	in_air *= (1.0-1.0/MTBF)

# lindbergh_two_engine.py ------------------------------

[107]

29%

[108]

counters	pick up
29		5
28		4
27		3
26		2
25		1
24		you're doomed, pick up anything you like

[109]

You're doomed.

[110]

That said, don't think the people with money know any more about what they're supposed to be doing, just because they have money, than they do about what you're supposed to be doing. See Donkey Kong and Me (http://www.dadhacker.com/blog/?p=987 referenced from http://games.slashdot.org/article.pl?sid=08/03/08/194249) for how Atari blew their big lead in computer games.

I have a Ph.D. and have been in the work force for 30yrs. You would think that you'd hire a Ph.D. only if you knew what to do with him. Only 5 of those years have I worked for someone who could both manage people and knew enough to evaluate what I was doing. Much of the other 25 yrs I worked for someone who knew neither. Don't expect if you're competent, that you'll work for someone who knows what to do with you. It's no Dilbert joke. It's your life these people are going to mess up. After accepting one of these jobs, sold your house, left your friends and arrived in a new town, only to find you're working for someone who doesn't have a clue, you often don't have enough money or time to start looking again. You have to make the best of working for an idiot. But there is no best of working for an idiot; there may be ways of finding a least worst way, but don't count on it. And you've left your friends and aquaintances, your social and hobby network.

[111]

welcome() needs no parameters. You're just telling it to do the same thing no matter how the program is run.

There are no return values: the function just prints the rules to the screen.

[112]

resp is a string. This program needs input from the user that is an int.

[113]

#----
#player's turn
#
#Invalid user input which must be handled (later)
#strings eg "5d", "Homer Simpson"
#reals   eg 2.71828
#out of range input, ie input != 1..max_num
#
resp = raw_input("Your turn. There are %d counters. How many counters do you want to pick up? " % counters)
#print "you're picking up %s counters" % resp
players_resp = int(resp)
counters = counters - players_resp
print "you're picking up %d counters. there are %d counters left" % (players_resp, counters)

#----

[114]

  • If it can, leave m(n+1) counters
  • If it can't leave m(n+1) counters, it should pick up a small number of counters. Use a random number generator to pick up min_num..max_num/2 counters.

[115]

def computers_turn(my_counter, my_max_num):
	"""
	computers_turn(int,int)
	returns int

	Kirby (C) 2008, released under GPL

	algorithm:
	for the computer to win, the function subtracts a number 1..my_max_num 
		to leave a multiple of (my_max_num +1)

	if the substraction cannot leave a multiple of (my_max_num +1), 
		the computer subtracts a random number 1..my_max_num/2
	"""

	result = 1
	return result

#--computers_turn-----

[116]

# ./nim.py
Nim:
here are the rules

main: You're allowed to pick up 1..7 counters
main: counters 54

Your turn. There are 54 counters. How many counters do you want to pick up? 6
you're picking up 6 counters. there are 48 counters left
the computer picked up 1 counters.

Your turn. There are 47 counters. How many counters do you want to pick up? 

[117]

The remainder after dividing by (max_num+1) is the number of counters to subtract to be in the winning position. Are you allowed to (in a position to) substract that many?

[118]

0

[119]

There is no possibility of getting a non-integer. If max_num is odd, max_num/2 is still an integer. You're doing integer arithmetic and the result will be an integer.

>>> 3/2
1

[120]

if (max_num=<1)

[121]

players will only be allowed to pick up 1 counter and will have no choice in what they play.

[122]

	import random

	#result = 1	#used before conditional branch code written 
	result = my_counter%(my_max_num + 1)
	if (result==0):
	        print "the computer is going to punt"
		#result = 1	#used before wrote line below
	        result = random.randint(min_num,my_max_num/2)
	else:
		print "you're doomed"

	return result

[123]

(counters==0).

Depending how you write the code you could have to detect (counters=<0). We'll write the code so that neither player can leave a negative number of counters. This will better mimick the game played by a pair of humans.

[124]

If the computer picks up the last counter, the loop will exit and the player will not get another turn. This is the correct action by the code.

If the player picks up the last counter, the computer will then be given a turn. This is incorrect.

[125]

The first requires modification of the calling code, the second requires modification of computers_turn()

[126]

#no winner yet
while (game not finished)
	#computer didn't win
	#since the computer got to play, the player didn't win either
	player's turn
	#player may or may not have won
	if (game not finished)
		#no winner yet
		computers_turn()
	else
		#player has won

#the game is over, so someone has won
#if it wasn't player, then it's the computer
declare winner

#no winner yet
while (game not finished)
	#computer didn't win
	#since the computer got to play, the player didn't win either
	player's turn
	#player may or may not have won
	computers_turn()
		#inside computers_turn()
		if (counters==0):
			#player has won
			return 0
		else 
			do normal play

#the game is over, so someone has won
#if it wasn't player, then it's the computer
declare winner

[127]

#! /usr/bin/python 
"""
	nim.py
	Kirby (C) 2008, released under GPL v3
	plays the game Nim (the subtraction game)
"""

def welcome():
	"""
	welcome()
	Kirby (C) 2008, released under GPL
	prints the rules for Nim.
	"""
	
	print "Nim:"
	print "Here are the rules:"
	print
	print "The computer will lay out a set of counters."
	print "You and the computer take turns removing counters."
	print "You will have the first turn."
	print "You're both allowed to take 1..n counters."
	print "The computer will announce the value of n at the beginning of the game."
	print "The player that picks up the last counter(s) wins."
	print "Good luck."
	print
#--welcome()---

def computers_turn(my_counter, my_max_num):
	"""
	computers_turn(int,int)
	returns int

	Kirby (C) 2008, released under GPL

	algorithm:
	for the computer to win, the function subtracts a number 1..my_max_num 
		to leave a multiple of (my_max_num +1)

	if the substraction cannot leave a multiple of (my_max_num +1), 
		the computer subtracts a random number 1..my_max_num/2 
	"""

	import random

	result = my_counter%(my_max_num + 1)
	if (result==0):
		print "the computer is going to punt"
		result = random.randint(min_num,my_max_num/2)
	else:
		print "you're doomed"
	
	#result = 1
	return result

#--computers_turn-----

#----------
#main()

import random

#user defined variables
#
#max_num is the maximum number of counters a player can pick up
#
#lower_limit_max_num must be 2 or greater for a sensible game.
#The program will crash at runtime if lower_limit_max_num is =<1
#
lower_limit_max_num = 5 
upper_limit_max_num = 9

min_num=1                       #minimum number of counters that players can pick up.
				#the algorithm assumes min_num==1. Don't change it unless you change the algorithm too.

upper_limit_counters = 80       #the width of the terminal
minimum_number_plays = 3	#determines the minimum number of counters laid down by the computer 

#------
#note: can't indent lines in main()

#------
welcome()

#-----
#find parameters for this game
max_num = random.randint(lower_limit_max_num, upper_limit_max_num)
print "main: You're allowed to pick up %d..%d counters" % (min_num, max_num)

counters = random.randint(minimum_number_plays * max_num, upper_limit_counters)
print "main: counters %d" % counters
print

winner = "computer"
while (counters != 0):
	#----
	#player's turn
	#
	#Invalid user input which must be handled (later)
	#strings eg "5d", "Homer Simpson"
	#reals   eg 2.71828
	#out of range input, ie input != min_num..max_num
	#
	resp = raw_input("Your turn. There are %d counters. How many do you want to pick up? " % counters)
	#print "you're picking up %s counters" % resp
	players_resp = int(resp)
	counters = counters - players_resp
	print "you're picking up %d counters. There are %d left." % (players_resp, counters)

	#----
	if (counters != 0):
		#computer's turn
		computers_play = computers_turn(counters, max_num)
		counters = counters - computers_play
		#print "counters %d " % counters
		print "the computer picked up %d counters. There are %d left." % (computers_play, counters)
		print
	else:
		winner = "player"

#-----
print "the " + winner + " has won!"
#player's turn
#resp = raw_input("Your turn. There are %d counters. How many counters do you want to pick up? " % counters)

#-nim.py---

[128]

players_resp = int(resp)

int() cant turn "5d" into an int.

[129]

def players_turn(l,u):
	"""
	#modelled on code from http://www.wellho.net/mouth/1236_Trying-things-in-Python.html
	Homer Simpson (C) 2008 homer@simpson.com,
	v1 Mar 2008, releaseed under GPL v3

	asks for user input of an integer l..u
	input outside this range, or input of a real or a string is not accepted
	and the user is asked for another input


	parameters
	l int
	u int

	returns int, the user's input

	"""
	val = 0 #prime the while loop to run by initialising val to an invalid value

	while ((val > u) or (val < l)):

	        #uval is the string represenation of the user entered value
	        #whether it was originally an integer, float or string.

	        uval = raw_input("input an integer in the range %d..%d: " % (l, u))
	        try:
	                #is uval the string representation of an integer?
	                val = int(uval)
	        except:
	                #no, it's a string or string representation of a real. Give the user feedback.
	                print uval + " is not an integer - try again."
	        else:
	                #yes, val is an integer
	                #if invalid value, then help user
	                if ((val > u) or (val < l)):
	                        print "error: val %d is outside range %d..%d" % (val, l, u)
	                else:
	                        # valid value of val. exit the while loop
	                        result = val

	return result
#--players_turn-------

[130]

no. However if the computer made such a play, it had no winning play to make.

[131]

Yes. If the player is using the winning strategy, since they go first, there is no strategy for the computer beyond picking a random number of counters. In that case, in the last round of the game, the player will be left with a number of counters in the range min_num..max_num.

[132]

yes

[133]

winner = "computer"
while (counters != 0):
	#----
	#player's turn
	#
	#Invalid user input which must be handled (later)
	#strings eg "5d", "Homer Simpson"
	#reals   eg 2.71828
	#out of range input, ie input != min_num..max_num

	#old code
	#resp = raw_input("Your turn. There are %d counters. How many do you want to pick up? " % counters)
	#print "you're picking up %s counters" % resp
	#players_resp = int(resp)

	#new code
	#the lower limit is min_num, what's the upper limit? max_num?
	#what if there are less counters than max_num? (If so, was the other player using a winning strategy?)
	players_resp = players_turn(min_num,min(counters, max_num))
	counters = counters - players_resp

	print "you're picking up %d counters. There are %d left." % (players_resp, counters)

[134]

15

[135]

nim_prompt(), where it will be reintiallised each time the function is called. (You could still have output_string in global namespace and update it, but it's better to keep as much of the data for nim_prompt() within the function as possible.)

[136]

#!/usr/bin/python

#functions

#new code for this file
def show_counters(number_counters):
	"""
		Evander Holyfield (C) 2008 
		License: GPL v3
		function: returns a string of "X"s, one for each counter
		parameter: int, number_counters: the number of counters
		returns: string containing number_counters of "X"s.
	"""
	result = ""
	for x in range(number_counters):
		result += "X"

	#print result	#for debugging
	return result

# show_counters----------------------------

def nim_prompt(play, counters):
	"""
		Cassius Clay (C) 2008
		License: GPL v3
		function: writes out a prompt for the user showing the state of play
			Here's a sample prompt


			XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXccccppppcccccppp
			    '    1    '    2    '    3    '    4  ^ '    5    '    
			Your turn. There are 43 counters. You can pick 1..9 counters: 

			Hopefully the points conveyed in this new prompt are 

			There are 43 counters currently on the table 
			The game started with 59 counters 
			The past plays (player - p, computer - c) are shown in order (3,5,4,4 counters respectively). 

	        #parameters
	        #play: number of counters picked up in the last play, leaving counters number of counters
	        #counters: current number of counters
	"""

	plays.append(play)
	print plays

	#new code for this file

	output_string = ""

	#generate the string to show the counters
	output_string += show_counters(counters)

	#leave the old code here until you know that show_counters() is working.
	#for x in range(counters):
	#        output_string += "X"

	print output_string

# nim_prompt-----------------

#global variables

plays=[]

#main()

nim_prompt(3,56)
nim_prompt(5,51)
nim_prompt(4,47)
nim_prompt(4,43)

# test_nim_prompt_2----------------------

[137]

# ./test_nim_prompt_2.py
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[3]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[3, 5]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[3, 5, 4]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[3, 5, 4, 4]

[138]

#!/usr/bin/python

#functions

#new code for this file
def show_counters(number_counters):
	"""
		Evander Holyfield (C) 2008 
		License: GPL v3
		function: returns a string of "X"s, one for each counter
		parameter: int, number_counters: the number of counters
		returns: string containing number_counters of "X"s.
	"""
	result = ""
	for x in range(number_counters):
		result += "X"

	#print result	#for debugging
	return result

# show_counters----------------------------

def nim_prompt(play, counters):
	"""
		Cassius Clay (C) 2008
		License: GPL v3
		function: writes out a prompt for the user showing the state of play
			Here's a sample prompt


			XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXccccppppcccccppp
			    '    1    '    2    '    3    '    4  ^ '    5    '    
			Your turn. There are 43 counters. You can pick 1..9 counters: 

			Hopefully the points conveyed in this new prompt are 

			There are 43 counters currently on the table 
			The game started with 59 counters 
			The past plays (player - p, computer - c) are shown in order (3,5,4,4 counters respectively). 

	        #parameters
	        #play: number of counters picked up in the last play, leaving counters number of counters
	        #counters: current number of counters
	"""
	#new code for this file

	output_string = ""

	#generate the string to show the counters
	output_string += show_counters(counters)

	#leave the old code here until you know that show_counters() is working.
	#for x in range(counters):
	#        output_string += "X"

	print output_string

	plays.append(play)
	print plays

# nim_prompt-----------------

#global variables

plays=[]

#main()

nim_prompt(3,56)
nim_prompt(5,51)
nim_prompt(4,47)
nim_prompt(4,43)

# test_nim_prompt_2----------------------

[139]

# ./test_nim_prompt_2.py
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[3]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[3, 5]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[3, 5, 4]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[3, 5, 4, 4]

[140]

	#output_string = ""
	output_string = show_counters(counters)

[141]

The player always goes first, so we know that the first play (the play with 3 counters) is the player. We know the number of entries in plays[] (from len(play)). If the number of entries is even, then the right most entry (the last play) is from the computer. If the number of entries is odd, the last play is from the player.

[142]

(odd_number % 2) returns 1
(even_number % 2) returns 0

test len(plays) to see if it's odd or even, then write the char for the player or for the computer

[143]

#!/usr/bin/python

#functions

def show_counters(number_counters):
	"""
		Evander Holyfield (C) 2008 
		License: GPL v3
		function: returns a string of "X"s, one for each counter
		parameter: int, number_counters: the number of counters
		returns: string containing number_counters of "X"s.
	"""
	result = ""
	for x in range(number_counters):
		result += "X"

	#print result	#for debugging
	return result

# show_counters----------------------------

def nim_prompt(play, counters):
	"""
		Cassius Clay (C) 2008
		License: GPL v3
		function: writes out a prompt for the user showing the state of play
			Here's a sample prompt


			XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXccccppppcccccppp
			    '    1    '    2    '    3    '    4  ^ '    5    '    
			Your turn. There are 43 counters. You can pick 1..9 counters: 

			Hopefully the points conveyed in this new prompt are 

			There are 43 counters currently on the table 
			The game started with 59 counters 
			The past plays (player - p, computer - c) are shown in order (3,5,4,4 counters respectively). 

	        #parameters
	        #play: number of counters picked up in the last play, leaving counters number of counters
	        #counters: current number of counters
	"""

	plays.append(play)
	print plays	#print is optional, just plays will output the contents of the list

	#new code for this file
	output_string = ""
	#generate the string to show the counters
	output_string += show_counters(counters)
	print output_string

	#new code for this file
	#who made the last play?
	if ((len(plays)% 2) == 0):
		print "c"
	else:
		print "p"

# nim_prompt-----------------

#global variables

plays=[]

#main()

#instead of running these to update plays[]
#just substitute the resulting value of plays[]
#nim_prompt(3,56)
#nim_prompt(5,51)
#nim_prompt(4,47)
plays=[3,5,4]

nim_prompt(4,43)

# test_nim_prompt_3----------------------

[144]

# ./test_nim_prompt_3.py
[3, 5, 4, 4]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
c
i.e. the computer made the last play

[145]

We can tell the number of entries in plays[] with len(plays) without asking the contents of any of the entries. Finding the value of each entry is a separate step.

[146]

Reading out our list will empty it and we will not longer have any record of the game.

[147]

Reverse the order of entries in plays[], write out the elements of plays[] in the order we want. Then restore plays[] to its original order, by applying the list.reverse() operation again.

[148]

in global namespace

[149]

for - the number of entries is known before you start looping.

[150]

#!/usr/bin/python

#functions

def reverse_append_plays():
	"""
		Tonia Harding (C) 2008
		License: GLP v3
		function: returns a string containing "c"s and "p"s needed for nim_prompt, 
			showing the player's and computer's plays
		parameters: none
		returns: string: see specs for nim_prompt()
		global variables: plays[]
	"""

	#who made the last play?
	if ((len(plays)% 2) == 0):
		#print "c"
		#new code for this file
		player_char="c"
	else:
		#print "p"
		#new code for this file
		player_char="p"

	#new code for this file
	plays.reverse()
	#initialise result
	result=""
	for play in plays:
		#print play
		for i in range(play):
			result += player_char

	#print result	#for debugging

	plays.reverse()
	
	return result

# reverse_append_plays---------------------

def show_counters(number_counters):
	"""
		Evander Holyfield (C) 2008 
		License: GPL v3
		function: returns a string of "X"s, one for each counter
		parameter: int, number_counters: the number of counters
		returns: string containing number_counters of "X"s.
	"""
	result = ""
	for x in range(number_counters):
		result += "X"

	#print result	#for debugging
	return result

# show_counters----------------------------

def nim_prompt(play, counters):
	"""
		Cassius Clay (C) 2008
		License: GPL v3
		function: writes out a prompt for the user showing the state of play
			Here's a sample prompt


			XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXccccppppcccccppp
			    '    1    '    2    '    3    '    4  ^ '    5    '    
			Your turn. There are 43 counters. You can pick 1..9 counters: 

			Hopefully the points conveyed in this new prompt are 

			There are 43 counters currently on the table 
			The game started with 59 counters 
			The past plays (player - p, computer - c) are shown in order (3,5,4,4 counters respectively). 

	        #parameters
	        #play: number of counters picked up in the last play, leaving counters number of counters
	        #counters: current number of counters
	"""

	plays.append(play)
 	#print plays     #("print" is optional, just "plays" will do)

	output_string = ""
	#generate the string to show the counters
	output_string += show_counters(counters)
	#print output_string

	#new code for this file
	output_string += reverse_append_plays()
	print output_string

# nim_prompt-----------------

#global variables

plays=[]

#main()

#instead of running these to update plays[]
#just substitute the resulting value of plays[]
#nim_prompt(3,56)
#nim_prompt(5,51)
#nim_prompt(4,47)
plays=[3,5,4]

nim_prompt(4,43)

# test_nim_prompt_4----------------------

[151]

# ./test_nim_prompt_4.py
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXcccccccccccccccc

[152]

#!/usr/bin/python

player_char="p"

if player_char=="p":
	player_char="c"
else:
	player_char="p"

print player_char
# exchange_player_char.py

[153]

./exchange_player_char.py
c

[154]

#!/usr/bin/python

#functions

#new code for this file
def exchange_player_char(p_char):
	"""
		Homer Simpson (C) 2008 homer@simpson.com
		License: GPL v3
		function: p_char is a "p" or a "c" - the value is swapped 
		parameters: string p_char - is either a "p" or a "c"
		returns: string - the exchanged value of p_char
	"""
	#exchange player_char after a play
	#print "exchange_player_char: " + p_char
	if p_char=="p":
	        result="c"
	else:
	        result="p"

	return result

# exchange_player_char------------


def reverse_append_plays():
	"""
		Tonia Harding (C) 2008
		License: GLP v3
		function: returns a string containing "c"s and "p"s needed for nim_prompt, 
			showing the player's and computer's plays
		parameters: none
		returns: string: see specs for nim_prompt()
		global variables: plays[]
	"""

	#who made the last play?
	if ((len(plays)% 2) == 0):
		#print "c"
		#new code for this file
		player_char="c"
	else:
		#print "p"
		#new code for this file
		player_char="p"

	plays.reverse()
	#initialise result
	result=""
	for play in plays:
		#print play
		for i in range(play):
			result += player_char

		#new code for this file
		player_char=exchange_player_char(player_char)

	#print result	#for debugging

	plays.reverse()
	
	return result

# reverse_append_plays---------------------

def show_counters(number_counters):
	"""
		Evander Holyfield (C) 2008 
		License: GPL v3
		function: returns a string of "X"s, one for each counter
		parameter: int, number_counters: the number of counters
		returns: string containing number_counters of "X"s.
	"""
	result = ""
	for x in range(number_counters):
		result += "X"

	#print result	#for debugging
	return result

# show_counters----------------------------

def nim_prompt(play, counters):
	"""
		Cassius Clay (C) 2008
		License: GPL v3
		function: writes out a prompt for the user showing the state of play
			Here's a sample prompt


			XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXccccppppcccccppp
			    '    1    '    2    '    3    '    4  ^ '    5    '    
			Your turn. There are 43 counters. You can pick 1..9 counters: 

			Hopefully the points conveyed in this new prompt are 

			There are 43 counters currently on the table 
			The game started with 59 counters 
			The past plays (player - p, computer - c) are shown in order (3,5,4,4 counters respectively). 

	        #parameters
	        #play: number of counters picked up in the last play, leaving counters number of counters
	        #counters: current number of counters
	"""

	plays.append(play)
 	#print plays     #("print" is optional, just "plays" will do)

	output_string = ""
	#generate the string to show the counters
	output_string += show_counters(counters)
	#print output_string

	output_string += reverse_append_plays()
	print output_string

# nim_prompt-----------------

#global variables

plays=[]

#main()

#instead of running these to update plays[]
#just substitute the resulting value of plays[]
#nim_prompt(3,56)
#nim_prompt(5,51)
#nim_prompt(4,47)
plays=[3,5,4]

nim_prompt(4,43)

# test_nim_prompt_5----------------------

[155]

# ./test_nim_prompt_5.py
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXccccppppcccccppp

[156]

#!/usr/bin/python

#functions

def exchange_player_char(p_char):
	"""
		Homer Simpson (C) 2008 homer@simpson.com
		License: GPL v3
		function: p_char is a "p" or a "c" - the value is swapped 
		parameters: string p_char - is either a "p" or a "c"
		returns: string - the exchanged value of p_char
	"""
	#exchange player_char after a play
	#print "exchange_player_char: " + p_char
	if p_char=="p":
	        result="c"
	else:
	        result="p"

	return result

# exchange_player_char------------


def reverse_append_plays():
	"""
		Tonia Harding (C) 2008
		License: GLP v3
		function: returns a string containing "c"s and "p"s needed for nim_prompt, 
			showing the player's and computer's plays
		parameters: none
		returns: string: see specs for nim_prompt()
		global variables: plays[]
	"""

	#who made the last play?
	if ((len(plays)% 2) == 0):
		player_char="c"
		#new code for this file
		not_player_char="p"
	else:
		player_char="p"
		#new code for this file
		not_player_char="c"

	plays.reverse()
	#initialise result
	result=""
	for play in plays:
		#print play
		for i in range(play):
			result += player_char

		#comment out original call
		#player_char=exchange_player_char(player_char)
		#new code for this file
		temp = player_char
		player_char = not_player_char
		not_player_char = temp

	#print result	#for debugging

	plays.reverse()
	
	return result

# reverse_append_plays---------------------

def show_counters(number_counters):
	"""
		Evander Holyfield (C) 2008 
		License: GPL v3
		function: returns a string of "X"s, one for each counter
		parameter: int, number_counters: the number of counters
		returns: string containing number_counters of "X"s.
	"""
	result = ""
	for x in range(number_counters):
		result += "X"

	#print result	#for debugging
	return result

# show_counters----------------------------

def nim_prompt(play, counters):
	"""
		Cassius Clay (C) 2008
		License: GPL v3
		function: writes out a prompt for the user showing the state of play
			Here's a sample prompt


			XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXccccppppcccccppp
			    '    1    '    2    '    3    '    4  ^ '    5    '    
			Your turn. There are 43 counters. You can pick 1..9 counters: 

			Hopefully the points conveyed in this new prompt are 

			There are 43 counters currently on the table 
			The game started with 59 counters 
			The past plays (player - p, computer - c) are shown in order (3,5,4,4 counters respectively). 

	        #parameters
	        #play: number of counters picked up in the last play, leaving counters number of counters
	        #counters: current number of counters
	"""

	plays.append(play)
 	#print plays     #("print" is optional, just "plays" will do)

	output_string = ""
	#generate the string to show the counters
	output_string += show_counters(counters)
	#print output_string

	output_string += reverse_append_plays()
	print output_string

# nim_prompt-----------------

#global variables

plays=[]

#main()

#instead of running these to update plays[]
#just substitute the resulting value of plays[]
#nim_prompt(3,56)
#nim_prompt(5,51)
#nim_prompt(4,47)
plays=[3,5,4]

nim_prompt(4,43)

# test_nim_prompt_6----------------------

[157]

The prompt displays the wrong number of plays.

[158]

The nim_prompt() code is not told about the player's plays. You can append() the player's play to plays[] in main() (leave a comment as to what you've done).

[159]

#! /usr/bin/python 
"""
	nim.py
	Kirby (C) 2008, released under GPL v3
	plays the game Nim (the subtraction game)
"""

def welcome():
	"""
	welcome()
	Kirby (C) 2008, released under GPL
	prints the rules for Nim.
	"""
	
	print "Nim:"
	print "Here are the rules:"
	print
	print "The computer will lay out a set of counters."
	print "You and the computer take turns removing counters."
	print "You will have the first turn."
	print "You're both allowed to take 1..n counters."
	print "The computer will announce the value of n at the beginning of the game."
	print "The player that picks up the last counter(s) wins."
	print "Good luck."
	print
#--welcome()---

def computers_turn(my_counter, my_max_num):
	"""
	computers_turn(int,int)
	returns int

	Kirby (C) 2008, released under GPL

	algorithm:
	for the computer to win, the function subtracts a number 1..my_max_num 
		to leave a multiple of (my_max_num +1)

	if the substraction cannot leave a multiple of (my_max_num +1), 
		the computer subtracts a random number 1..my_max_num/2 
	"""

	import random

	result = my_counter%(my_max_num + 1)
	if (result==0):
		print "the computer is going to punt"
		result = random.randint(min_num,my_max_num/2)
	else:
		print "you're doomed"
	
	return result

#--computers_turn-----

def players_turn(l,u):
	"""
	#modelled on code from http://www.wellho.net/mouth/1236_Trying-things-in-Python.html
	Homer Simpson (C) 2008 homer@simpson.com, 
	v1 Mar 2008, releaseed under GPL v3

	asks for user input of an integer l..u
	input outside this range, or input of a real or a string is not accepted 
	and the user is asked for another input 
	
	returns the user's input
	
	parameters
	l int
	u int

	returns int

	"""
	val = 0	#prime the while loop to run by initialising val to an invalid value

	while ((val > u) or (val < l)):
	
		#uval is the string represenation of the user entered value
		#whether it was originally an integer, float or string.
	
		uval = raw_input("Pick %d..%d counters: " % (l, u))
		try:
			#is uval the string representation of an integer?
			val = int(uval)
		except:
			#no, it's a string or string representation of a real. Give the user feedback.
			print uval + " is not an integer - try again."
		else:
			#yes, val is an integer
			#if invalid value, then help user
			if ((val > u) or (val < l)):
			 	print "error: val %d is outside range %d..%d" % (val, l, u)
			else:
				# valid value of val. exit the while loop
				result = val
	
	return result

#--players_turn-------

def exchange_player_char(p_char):
	"""
		Homer Simpson (C) 2008 homer@simpson.com
		License: GPL v3
		function: p_char is a "p" or a "c" - the value is swapped 
		parameters: string p_char - is either a "p" or a "c"
		returns: string - the exchanged value of p_char
	"""
	#exchange player_char after a play
	#print "exchange_player_char: " + p_char
	if p_char=="p":
	        result="c"
	else:
	        result="p"

	return result

# exchange_player_char------------


def reverse_append_plays():
	"""
		Tonia Harding (C) 2008
		License: GLP v3
		function: returns a string containing "c"s and "p"s needed for nim_prompt, 
			showing the player's and computer's plays
		parameters: none
		returns: string: see specs for nim_prompt()
		global variables: plays[]
	"""

	#who made the last play?
	if ((len(plays)% 2) == 0):
		player_char="c"
		not_player_char="p"
	else:
		player_char="p"
		not_player_char="c"

	plays.reverse()
	#initialise result
	result=""
	for play in plays:
		#print play
		for i in range(play):
			result += player_char

		#comment out original call
		#player_char=exchange_player_char(player_char)
		temp = player_char
		player_char = not_player_char
		not_player_char = temp

	#print result	#for debugging

	plays.reverse()
	
	return result

# reverse_append_plays---------------------

def show_counters(number_counters):
	"""
		Evander Holyfield (C) 2008 
		License: GPL v3
		function: returns a string of "X"s, one for each counter
		parameter: int, number_counters: the number of counters
		returns: string containing number_counters of "X"s.
	"""
	result = ""
	for x in range(number_counters):
		result += "X"

	#print result	#for debugging
	return result

# show_counters----------------------------

def nim_prompt(play, counters):
	"""
		Cassius Clay (C) 2008
		License: GPL v3
		function: writes out a prompt for the user showing the state of play
			Here's a sample prompt


			XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXccccppppcccccppp
			    '    1    '    2    '    3    '    4  ^ '    5    '    
			Your turn. There are 43 counters. You can pick 1..9 counters: 

			Hopefully the points conveyed in this new prompt are 

			There are 43 counters currently on the table 
			The game started with 59 counters 
			The past plays (player - p, computer - c) are shown in order (3,5,4,4 counters respectively). 

	        #parameters
	        #play: number of counters picked up in the last play, leaving counters number of counters
	        #counters: current number of counters
	"""

	plays.append(play)
 	#print plays     #("print" is optional, just "plays" will do)

	output_string = ""
	#generate the string to show the counters
	output_string += show_counters(counters)
	#print output_string

	output_string += reverse_append_plays()
	print output_string

# nim_prompt-----------------

#----------
#main()

import random

#user defined variables
#
#max_num is the maximum number of counters a player can pick up
#
#lower_limit_max_num must be 2 or greater for a sensible game.
#The program will crash at runtime if lower_limit_max_num is =<1

lower_limit_max_num = 5 
upper_limit_max_num = 9

min_num=1			#minimum number of counters that players can pick up.
				#the algorithm assumes min_num==1. Don't change it unless you change the algorithm too.

upper_limit_counters = 80       #the width of the terminal
minimum_number_plays = 3	#determines the minimum number of counters laid down by the computer	

#new code for this file
#global variables
plays=[]

#------
#note: can't indent lines in main()

#------
welcome()

#-----
#find parameters for this game
max_num = random.randint(lower_limit_max_num, upper_limit_max_num)
print "main: You're allowed to pick up %d..%d counters" % (min_num, max_num)

counters = random.randint(minimum_number_plays * max_num, upper_limit_counters)
print "main: counters %d" % counters
print

winner = "computer"
while (counters != 0):
	#----
	#player's turn
	#
	#Invalid user input which must be handled (later)
	#strings eg "5d", "Homer Simpson"
	#reals   eg 2.71828
	#out of range input, ie input != min_num..max_num

	print "Your turn. There are %d counters." % counters,
	#the lower limit is min_num, what's the upper limit? max_num?
	#what if there are less counters than max_num? (If so, was the other player using a winning strategy?)
	players_resp = players_turn(min_num,min(counters, max_num))
	counters = counters - players_resp

	#new code for this file
	#nim_prompt(players_resp,counters)
	#if you aren't going to display the prompt, you still need to update plays[]
	plays.append(players_resp)

	print "you're picking up %d counters. There are %d left." % (players_resp, counters)

	#----
	if (counters != 0):
		#computer's turn
		computers_play = computers_turn(counters, max_num)
		counters = counters - computers_play
		#print "counters %d " % counters
		#new code for this file
		nim_prompt(computers_play,counters)
		print "the computer picked up %d counters. There are %d left." % (computers_play, counters)
		print
	else:
		winner = "player"

#-----
print "the " + winner + " has won!"
#player's turn
#resp = raw_input("Your turn. There are %d counters. How many counters do you want to pick up? " % counters)

#-nim_2.py---

[160]

#! /usr/bin/python 
"""
	nim.py
	Kirby (C) 2008, released under GPL v3
	plays the game Nim (the subtraction game)
"""

def welcome():
	"""
	welcome()
	Kirby (C) 2008, released under GPL
	prints the rules for Nim.
	"""
	
	print "Nim:"
	print "Here are the rules:"
	print
	print "The computer will lay out a set of counters."
	print "You and the computer take turns removing counters."
	print "You will have the first turn."
	print "You're both allowed to take 1..n counters."
	print "The computer will announce the value of n at the beginning of the game."
	print "The player that picks up the last counter(s) wins."
	print "Good luck."
	print
#--welcome()---

def computers_turn(my_counter, my_max_num):
	"""
	computers_turn(int,int)
	returns int

	Kirby (C) 2008, released under GPL

	algorithm:
	for the computer to win, the function subtracts a number 1..my_max_num 
		to leave a multiple of (my_max_num +1)

	if the substraction cannot leave a multiple of (my_max_num +1), 
		the computer subtracts a random number 1..my_max_num/2 
	"""

	import random

	result = my_counter%(my_max_num + 1)
	if (result==0):
		print "the computer is going to punt"
		result = random.randint(min_num,my_max_num/2)
	else:
		print "you're doomed"
	
	return result

#--computers_turn-----

def players_turn(l,u):
	"""
	#modelled on code from http://www.wellho.net/mouth/1236_Trying-things-in-Python.html
	Homer Simpson (C) 2008 homer@simpson.com, 
	v1 Mar 2008, releaseed under GPL v3

	asks for user input of an integer l..u
	input outside this range, or input of a real or a string is not accepted 
	and the user is asked for another input 
	
	returns the user's input
	
	parameters
	l int
	u int

	returns int

	"""
	val = 0	#prime the while loop to run by initialising val to an invalid value

	while ((val > u) or (val < l)):
	
		#uval is the string represenation of the user entered value
		#whether it was originally an integer, float or string.
	
		uval = raw_input("Pick %d..%d counters: " % (l, u))
		try:
			#is uval the string representation of an integer?
			val = int(uval)
		except:
			#no, it's a string or string representation of a real. Give the user feedback.
			print uval + " is not an integer - try again."
		else:
			#yes, val is an integer
			#if invalid value, then help user
			if ((val > u) or (val < l)):
			 	print "error: val %d is outside range %d..%d" % (val, l, u)
			else:
				# valid value of val. exit the while loop
				result = val
	
	return result

#--players_turn-------

def exchange_player_char(p_char):
	"""
		Homer Simpson (C) 2008 homer@simpson.com
		License: GPL v3
		function: p_char is a "P" or a "c" - the value is swapped 
		parameters: string p_char - is either a "P" or a "c"
		returns: string - the exchanged value of p_char
	"""
	#exchange player_char after a play
	#print "exchange_player_char: " + p_char
	if p_char=="P":
		result="c"
	else:
		result="P"

	return result

# exchange_player_char------------

def reverse_append_plays():
	"""
		Tonia Harding (C) 2008
		License: GLP v3
		function: returns a string containing "c"s and "P"s needed for nim_prompt, 
			showing the player's and computer's plays
		parameters: none
		returns: string: see specs for nim_prompt()
		global variables: plays[]
	"""

	#who made the last play?
	if ((len(plays)% 2) == 0):
		player_char="c"
		not_player_char="P"
	else:
		player_char="P"
		not_player_char="c"

	plays.reverse()
	#initialise result
	result=""
	for play in plays:
		#print play
		for i in range(play):
			result += player_char

		#comment out original call
		#player_char=exchange_player_char(player_char)
		temp = player_char
		player_char = not_player_char
		not_player_char = temp

	plays.reverse()

	#print result	#for debugging
	return result

# reverse_append_plays---------------------

#def pop_plays(play, counters):
def pop_plays():
	"""
		Tonia Harding (C) 2008
		License: GLP v3
		function: returns a string containing "c"s and "P"s needed for nim_prompt, 
			showing the player's and computer's plays
		parameters: none
		returns: string: see specs for nim_prompt()
		global variables: plays[]
	"""

	#initialise result
	result=""
	#make copy of plays[]
	temp_copy_plays=plays[:]

	#who made the last play?
	if ((len(plays)% 2) == 0):
		player_char="c"
		not_player_char="P"
	else:
		player_char="P"
		not_player_char="c"

	#while (last_play=temp_copy_plays.pop()): #doesn't work in python
	while (len(temp_copy_plays) > 0):	#this works
	#for i in range(len(temp_copy_plays)):	#so does this
		last_play=temp_copy_plays.pop()
		for i in range(last_play):
			result += player_char

		#player_char=swap_player_char(player_char) #not called anymore
		#standard way to swap two values, requires a 3rd variable.
		temp = player_char
		player_char = not_player_char
		not_player_char = temp

	#print result

	return result

# pop_plays---------------------

def show_counters(number_counters):
	"""
		Evander Holyfield (C) 2008 
		License: GPL v3
		function: returns a string of "X"s, one for each counter
		parameter: int, number_counters: the number of counters
		returns: string containing number_counters of "X"s.
	"""
	result = ""
	for x in range(number_counters):
		result += "X"

	#print result	#for debugging
	return result

# show_counters----------------------------

def nim_prompt(play, counters):
	"""
		Cassius Clay (C) 2008
		License: GPL v3
		function: writes out a prompt for the user showing the state of play
			Here's a sample prompt


			XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXccccppppcccccppp
			    '    1    '    2    '    3    '    4  ^ '    5    '    
			Your turn. There are 43 counters. You can pick 1..9 counters: 

			Hopefully the points conveyed in this new prompt are 

			There are 43 counters currently on the table 
			The game started with 59 counters 
			The past plays (player - p, computer - c) are shown in order (3,5,4,4 counters respectively). 

		#parameters
		#play: number of counters picked up in the last play, leaving counters number of counters
		#counters: current number of counters
	"""

	plays.append(play)
	#print "nim_prompt:"
 	#print plays     #("print" is optional, just "plays" will do)

	output_string = ""
	#generate the string to show the counters
	output_string += show_counters(counters)
	#print output_string

	#output_string += reverse_append_plays()
	#new code
	output_string += pop_plays()
	print output_string

# nim_prompt-----------------

#----------
#main()

import random

#user defined variables
#
#max_num is the maximum number of counters a player can pick up
#
#lower_limit_max_num must be 2 or greater for a sensible game.
#The program will crash at runtime if lower_limit_max_num is =<1

lower_limit_max_num = 5 
upper_limit_max_num = 9

min_num=1			#minimum number of counters that players can pick up.
				#the algorithm assumes min_num==1. Don't change it unless you change the algorithm too.

upper_limit_counters = 80       #the width of the terminal
minimum_number_plays = 3	#determines the minimum number of counters laid down by the computer

#new code for this file
#global variables
plays=[]

#------
#note: can't indent lines in main()

#------
welcome()

#-----
#find parameters for this game
max_num = random.randint(lower_limit_max_num, upper_limit_max_num)
print "main: You're allowed to pick up %d..%d counters" % (min_num, max_num)

counters = random.randint(minimum_number_plays * max_num, upper_limit_counters)
print "main: counters %d" % counters
print

winner = "computer"
while (counters != 0):
	#----
	#player's turn
	#
	#Invalid user input which must be handled (later)
	#strings eg "5d", "Homer Simpson"
	#reals   eg 2.71828
	#out of range input, ie input != min_num..max_num

	print "Your turn. There are %d counters." % counters,
	#the lower limit is min_num, what's the upper limit? max_num?
	#what if there are less counters than max_num? 
		#(If so, was the other player using a winning strategy?)
	players_resp = players_turn(min_num,min(counters, max_num))
	counters = counters - players_resp

	#new code for this file
	#nim_prompt(players_resp,counters)
	#if you aren't going to display the prompt, you still need to update plays[]
	plays.append(players_resp)

	print "you're picking up %d counters. There are %d left." % (players_resp, counters)

	#----
	if (counters != 0):
		#computer's turn
		computers_play = computers_turn(counters, max_num)
		counters = counters - computers_play
		#print "counters %d " % counters
		nim_prompt(computers_play,counters)
		print "the computer picked up %d counters. There are %d left." % (computers_play, counters)
		print
	else:
		winner = "player"

print "the " + winner + " has won!"

#-nim_3.py---

[161]

From the length of output_string. The position of the "^" is the value of counters

[162]

in nim_prompt()

[163]

#! /usr/bin/python

#functions

def set_legend(length, last_counter):
	"""
	Mike Tyson (C) 2008
	Released under GPL v3
	parameters
	        length: length of legend
	        last_counter: position of last counter available
	returns
	        legend_string, a string containing the legend

	An example legend_string is
	"    '    1    '    2    '    3    '    4  ^ '    5    '    "
	        of length 59
	        with markers every 10 places
	        with minor markers at every 5th place
	        with a "^" to indicate the position of the last counter (here 43)
	"""
	legend_list=[]
	#fill with blanks
	for i in range(length):
	        legend_list.append(" ")

	for i in range(0,length,5):
	        legend_list[i] = "'"

	for i in range(0,length,10):
	        legend_list[i] = repr(i/10)

	legend_list[last_counter] = "^"
	#print legend_list

	legend_string=''.join(legend_list)
	#print legend_string

	return legend_string

# set_legend()----------------------------

#main()
counters = 43
legend_length = 59
print set_legend(legend_length, counters)

# test_legend.py ---------------

[164]

The first "X" lines up with the "0" and the indicated position of the characters of output_string are off by one position. If you'd been looking you could have seen that the output from the specified output (at the top of this section) and the actual output (just above). (I wrote the spec about 2 months before I coded it up, and by then had forgotten what I was supposed to be coding.) Possible simple fixes are

  • move output_string one space to the right. You can do this by initializing output_string to " " (a single blank) rather than "" (an empty string).
  • move legend_string one space to the left. You can do this by removing the first entry in legend_list[] before you convert it to a string. The instruction for removing the entry at position index from a list is
    del list[index]
    

[165]

#! /usr/bin/python 
"""
	nim.py
	Kirby (C) 2008, released under GPL v3
	plays the game Nim (the subtraction game)
"""

def welcome():
	"""
	welcome()
	Kirby (C) 2008, released under GPL
	prints the rules for Nim.
	"""
	
	print "Nim:"
	print "Here are the rules:"
	print
	print "The computer will lay out a set of counters."
	print "You and the computer take turns removing counters."
	print "You will have the first turn."
	print "You're both allowed to take 1..n counters."
	print "The computer will announce the value of n at the beginning of the game."
	print "The player that picks up the last counter(s) wins."
	print "Good luck."
	print
#--welcome()---

def computers_turn(my_counter, my_max_num):
	"""
	computers_turn(int,int)
	returns int

	Kirby (C) 2008, released under GPL

	algorithm:
	for the computer to win, the function subtracts a number 1..my_max_num 
		to leave a multiple of (my_max_num +1)

	if the substraction cannot leave a multiple of (my_max_num +1), 
		the computer subtracts a random number 1..my_max_num/2 
	"""

	import random

	result = my_counter%(my_max_num + 1)
	if (result==0):
		print "the computer is going to punt"
		result = random.randint(min_num,my_max_num/2)
	else:
		print "you're doomed"
	
	return result

#--computers_turn-----

def players_turn(l,u):
	"""
	#modelled on code from http://www.wellho.net/mouth/1236_Trying-things-in-Python.html
	Homer Simpson (C) 2008 homer@simpson.com, 
	v1 Mar 2008, releaseed under GPL v3

	asks for user input of an integer l..u
	input outside this range, or input of a real or a string is not accepted 
	and the user is asked for another input 
	
	returns the user's input
	
	parameters
	l int
	u int

	returns int

	"""
	val = 0	#prime the while loop to run by initialising val to an invalid value

	while ((val > u) or (val < l)):
	
		#uval is the string represenation of the user entered value
		#whether it was originally an integer, float or string.
	
		uval = raw_input("Pick %d..%d counters: " % (l, u))
		try:
			#is uval the string representation of an integer?
			val = int(uval)
		except:
			#no, it's a string or string representation of a real. Give the user feedback.
			print uval + " is not an integer - try again."
		else:
			#yes, val is an integer
			#if invalid value, then help user
			if ((val > u) or (val < l)):
			 	print "error: val %d is outside range %d..%d" % (val, l, u)
			else:
				# valid value of val. exit the while loop
				result = val
	
	return result

#--players_turn-------

def exchange_player_char(p_char):
	"""
		Homer Simpson (C) 2008 homer@simpson.com
		License: GPL v3
		function: p_char is a "P" or a "c" - the value is swapped 
		parameters: string p_char - is either a "P" or a "c"
		returns: string - the exchanged value of p_char
	"""
	#exchange player_char after a play
	#print "exchange_player_char: " + p_char
	if p_char=="P":
		result="c"
	else:
		result="P"

	return result

# exchange_player_char------------

def reverse_append_plays():
	"""
		Tonia Harding (C) 2008
		License: GLP v3
		function: returns a string containing "c"s and "P"s needed for nim_prompt, 
			showing the player's and computer's plays
		parameters: none
		returns: string: see specs for nim_prompt()
		global variables: plays[]
	"""

	#who made the last play?
	if ((len(plays)% 2) == 0):
		player_char="c"
		not_player_char="P"
	else:
		player_char="P"
		not_player_char="c"

	plays.reverse()
	#initialise result
	result=""
	for play in plays:
		#print play
		for i in range(play):
			result += player_char

		#comment out original call
		#player_char=exchange_player_char(player_char)
		temp = player_char
		player_char = not_player_char
		not_player_char = temp

	plays.reverse()

	#print result	#for debugging
	return result

# reverse_append_plays---------------------

#def pop_plays(play, counters):
def pop_plays():
	"""
		Tonia Harding (C) 2008
		License: GLP v3
		function: returns a string containing "c"s and "P"s needed for nim_prompt, 
			showing the player's and computer's plays
		parameters: none
		returns: string: see specs for nim_prompt()
		global variables: plays[]
	"""

	#initialise result
	result=""
	#make copy of plays[]
	temp_copy_plays=plays[:]

	#who made the last play?
	if ((len(plays)% 2) == 0):
		player_char="c"
		not_player_char="P"
	else:
		player_char="P"
		not_player_char="c"

	#while (last_play=temp_copy_plays.pop()): #doesn't work in python
	while (len(temp_copy_plays) > 0):	#this works
	#for i in range(len(temp_copy_plays)):	#so does this
		last_play=temp_copy_plays.pop()
		for i in range(last_play):
			result += player_char

		#player_char=swap_player_char(player_char) #not called anymore
		#standard way to swap two values, requires a 3rd variable.
		temp = player_char
		player_char = not_player_char
		not_player_char = temp

	#print result

	return result

# pop_plays---------------------

def show_counters(number_counters):
	"""
		Evander Holyfield (C) 2008 
		License: GPL v3
		function: returns a string of "X"s, one for each counter
		parameter: int, number_counters: the number of counters
		returns: string containing number_counters of "X"s.
	"""
	result = ""
	for x in range(number_counters):
		result += "X"

	#print result	#for debugging
	return result

# show_counters----------------------------

def nim_prompt(play, counters):
	"""
		Cassius Clay (C) 2008
		License: GPL v3
		function: writes out a prompt for the user showing the state of play
			Here's a sample prompt


			XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXccccppppcccccppp
			    '    1    '    2    '    3    '    4  ^ '    5    '    
			Your turn. There are 43 counters. You can pick 1..9 counters: 

			Hopefully the points conveyed in this new prompt are 

			There are 43 counters currently on the table 
			The game started with 59 counters 
			The past plays (player - p, computer - c) are shown in order (3,5,4,4 counters respectively). 

		#parameters
		#play: number of counters picked up in the last play, leaving counters number of counters
		#counters: current number of counters
	"""

	plays.append(play)
	#print "nim_prompt:"
 	#print plays     #("print" is optional, just "plays" will do)

	output_string = ""
	#new code for this file
	#output_string = " "	#one solution to aligning the output from set_legend() with output_string

	#generate the string to show the counters
	output_string += show_counters(counters)
	#print output_string

	#output_string += reverse_append_plays()
	output_string += pop_plays()
	print output_string

	#new code for this file
	print set_legend(len(output_string), counters)
	

# nim_prompt-----------------

#new code for this file
def set_legend(length, last_counter):
	"""
	Mike Tyson (C) 2008
	Released under GPL v3
	parameters
		length: length of legend
		last_counter: position of last counter available
	returns 
		result=legend_string, a string containing the legend

	An example legend_string is
	"    '    1    '    2    '    3    '    4  ^ '    5    '    "
		of length 59
		with markers every 10 places
		with minor markers at every 5th place
		with a "^" to indicate the position of the last counter (here 43)
	"""
	legend_list=[]
	#fill with blanks
	for i in range(length):
		legend_list.append(" ")

	for i in range(0,length,5):
		legend_list[i] = "'"

	for i in range(0,length,10):
		legend_list[i] = repr(i/10)

	legend_list[last_counter] = "^"
	#remove first entry
	del legend_list[0]	#another solution to aligning the output of legend_list with output_string
	#print legend_list

	legend_string=''.join(legend_list)
	#print legend_string
	result=legend_string

	return result

# set_legend()----------------------------

#main()

import random

#user defined variables
#
#max_num is the maximum number of counters a player can pick up
#
#lower_limit_max_num must be 2 or greater for a sensible game.
#The program will crash at runtime if lower_limit_max_num is =<1

lower_limit_max_num = 5 
upper_limit_max_num = 9

min_num=1			#minimum number of counters that players can pick up.
				#the algorithm assumes min_num==1. Don't change it unless you change the algorithm too.

upper_limit_counters = 80       #the width of the terminal
minimum_number_plays = 3	#determines the minimum number of counters laid down by the computer	

#global variables
plays=[]

#------
#note: can't indent lines in main()

#------
welcome()

#-----
#find parameters for this game
max_num = random.randint(lower_limit_max_num, upper_limit_max_num)
print "main: You're allowed to pick up %d..%d counters" % (min_num, max_num)

counters = random.randint(minimum_number_plays * max_num, upper_limit_counters)
print "main: counters %d" % counters
print

winner = "computer"
while (counters != 0):
	#----
	#player's turn
	#
	#Invalid user input which must be handled (later)
	#strings eg "5d", "Homer Simpson"
	#reals   eg 2.71828
	#out of range input, ie input != min_num..max_num

	print "Your turn. There are %d counters." % counters,
	#the lower limit is min_num, what's the upper limit? max_num?
	#what if there are less counters than max_num? 
		#(If so, was the other player using a winning strategy?)
	players_resp = players_turn(min_num,min(counters, max_num))
	counters = counters - players_resp

	#nim_prompt(players_resp,counters)
	#if you aren't going to display the prompt, you still need to update plays[]
	plays.append(players_resp)

	print "you're picking up %d counters. There are %d left." % (players_resp, counters)

	#----
	if (counters != 0):
		#computer's turn
		computers_play = computers_turn(counters, max_num)
		counters = counters - computers_play
		#print "counters %d " % counters
		nim_prompt(computers_play,counters)
		print "the computer picked up %d counters. There are %d left." % (computers_play, counters)
		print
	else:
		winner = "player"

print "the " + winner + " has won!"

#-nim_4.py---

[166]

80% play badly: have the computer generate random numbers 0..4. Pick any number (say 3): if this is the random number play well; the other 4 numbers play badly.

10% play badly: have the computer generate random numbers 0..9. Pick any number (say 3): if this is the random number play well; the other 9 numbers play badly.

[167]

#! /usr/bin/python

def play_at_level(level, counters, max_num):

	if (level==0):	
		#result = computer_play_expert(counters, max_num)       #returns an int, the number of counters to pick up
		print "play_at_level: %d" %level
		result = 8

	elif (level==1):
		print "play_at_level: %d" %level
		#call random function
		result = 2

	elif (level==2):
		print "play_at_level: %d" %level
		#result = computer_play_random(counters, max_num)       #returns an int, the number of counters to pick up
		result = 3

	else:
		print "play_at_level: ERROR: level %d unknown level" %level
		result = 4

	return result

#main()

max_num=9
counters=43
level=1

turn = play_at_level(level,counters,max_num)
print "the computer picked up %d counters" %turn
# set_level.py ----------------------

[168]

#! /usr/bin/python

def play_at_level(level, counters, max_num):
	import random

	if (level==0):
		#result = computer_play_expert(counters, max_num)	#returns an int, the number of counters to pick up
		print "play_at_level: %d" %level
		result = 8

	elif (level==1):
		print "play_at_level: %d" %level
		if (random.randrange(4)==0):
			print "Oooh, this is hard."
			#result = computer_play_random(counters, max_num)
			result = 5
		else:
			#result = computer_play_expert(counters, max_num)
			result = 2

	elif (level==2):
		print "play_at_level: %d" %level
		print "dum de dum"
		#result = computer_play_random(counters, max_num)	#returns an int, the number of counters to pick up
		result = 3

	else:
		print "play_at_level: ERROR: level %d unknown level" %level
		result = 0

	return result

#main()

max_num=9
counters=43
level=1

turn = play_at_level(level,counters,max_num)
print "the computer picked up %d counters" %turn
# set_level_3.py ----------------------

[169]

result=random.randrange(min_num,max_num)	#needs the parameter max_num (min_num is a user defined variable)

[170]

result=random.randrange(min_num,min(counters,max_num))	#needs the parameters max_num, counters (min_num is a user defined variable)

[171]

no

[172]

#! /usr/bin/python

def play_at_level(level, counters, max_num):
	import random

	if (level==0):
		#result = computer_play_expert(counters, max_num)	#returns an int, the number of counters to pick up
		print "play_at_level: %d" %level
		result = 8

	elif (level==1):
		print "play_at_level: %d" %level
		if (random.randrange(4)==0):
			print "Oooh, this is hard."
			result=random.randrange(min_num, max_num)
		else:
			#result = computer_play_expert(counters, max_num)
			result = 2

	elif (level==2):
		print "play_at_level: %d" %level
		print "dum de dum"
		result=random.randrange(min_num, max_num)

	else:
		print "play_at_level: ERROR: level %d unknown level" %level
		result = 0

	return result

#main()

max_num=9
counters=43
level=2

turn = play_at_level(level,counters,max_num)
print "the computer picked up %d counters" %turn
# set_level_4.py ----------------------

[173]

We'll rename computers_turn() to computer_play_expert()

[174]

  • copy the required user defined variables from set_level_4.py:main() to nim_5.py:main() (what are they? see which user defined variables are provided by nim.py:main()). Document the role of the variable(s).
  • in main() change the call computers_turn(counters, max_num) to play_at_level(level, counters, max_num). (better: comment out the old line and add a new line.)
  • change the name of computers_turn() to computer_play_expert() and change the documentation in the function to reflect the new name. (better: comment out the old names and add new names/documentation.) Should there be an uncommented string "computers_turn" be in the code anymore?
  • copy the code for play_at_level() into nim_5.py
  • in play_at_level() uncomment the calls to computer_play_expert() and comment the lines (returning a dummy value) just below it.

[175] root@routera:/scratch# echo "obase=10; ibase=10; 192*256*256*256 + 168*256*256 + 1*256 +3" | bc 3232235779

[176]

echo "obase=10; ibase=10;  192*256*256*256 + 168*256*256 + 2*256 +254" | bc
3232236286

[177]

echo "obase=16; ibase=10;  192*256*256*256 + 168*256*256 + 2*256 +254" | bc
C0A802FE

[178] If your IP is 10.1.2.7

echo "obase=10; ibase=10;  10*256*256*256 + 1*256*256 + 2*256 +7" | bc
167838215

ping 10.1.2.7

ping 167838215

echo "obase=256; ibase=10; 167838215" | bc
010 001 002 007

[179]

sin(30)=0.5,

[180]

By pythagorus; adjacent^2+opposite^2=hypoteneuse^2
adjacent^2=2^2-1^2
adjacent  = sqrt(3)
	  = 1.7321

[181]

cos(30)=adjacent/hypoteneuse
       =sqrt(3)/2
       =0.866

tan(30)=opposite/adjacent
       =1/sqrt(3)
       =0.577

[182]


	t .|
      o .  |
    p .    | opposite
  y .      | 
h .  45)   | 
 -----------
   adjacent

If the angle is 45°, then the other angle is also 45°. The opposite and adjacent are the same length, giving tan(45)=1.0.

[183]

sin(0)=0.0, sin(90)=1.0

[184]

.
.
>>> sin(radians(30))
0.49999999999999994
>>> cos(radians(60))
0.50000000000000011
>>> sin(radians(0))
0.0
>>> cos(radians(0))
1.0

[185]

distance to knuckles = 65cm
distance across knuckles = 7cm	#measured from the peak of the 2nd digit to the peak of the 5th digit

angle = atan(7/65)

>>> degrees(atan(7.0/65))
6.14662565964667

[186]

About 4

[187]

The earth rotates about it's axis 360° in 24hrs, or 15°/hr. Your hands show that the sun is about 12° above the horizon. It's just a little less than an hour before sunset.

[188]

The solstices, when the amount of day and night are equal (12hrs each), are in March and September. At the solstices, the sun rises and sets at 6am, 6pm. The time then is about 5pm.

[189]

Python 2.4.4 (#2, Mar 30 2007, 16:26:42) 
[GCC 3.4.6] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from math import *   #needed for trig functions
>>> 3474.0/363104		#sin of moon's diameter at perigee
0.009567506829999118		
>>> atan(3474.0/363104)
0.0095672149184776508	 #moon's diameter at perigee in radians
>>> degrees(atan(3474.0/363104))
0.54816103652336734	 #moon's diameter at perigee in degrees
>>> degrees(atan(3474.0/363104))*60
32.889662191402039	 #moon's diameter at perigee in minutes


>>> 3474.0/405696
0.0085630619971604361		#sin of moon's diameter at apogee
>>> atan(3474.0/405696)
0.0085628527079190126		#moon's diameter at apogee in radians
>>> degrees(atan(3474.0/405696))
0.49061532075592762		#moon's diameter at apogee in degrees
>>> degrees(atan(3474.0/405696))*60
29.436919245355657		#moon's diameter at apogee in minutes

Apogee  29.44min
Perigee 32.89min

[190]

Table 8. Total/Annular Lunar Eclipse at apogee/perigee

moon's position eclipse
apogee annular
perigee total

[191]

The base of the diagram on the right corresponds to what unknown dimension in the diagram on the left? You can determine the unknown dimension from Pythagorus.

[192]

The length of the diagonal on the base (by pythagorus)

diagonal=sqrt(2)*side
	=sqrt(2)*h*pi/2
	=sqrt(0.5)*h*pi
half_diagonal
	=sqrt(0.5)*h*pi/2

This allows us to calculate tan(A)

tan(A)=h/half_diagonal
      =h/(sqrt(0.5)*h*pi/2) 
      =1/(sqrt(0.5)*pi/2)
      =2*sqrt(2)/pi


pip:# python
from math import *
Python 2.4.4 (#2, Mar 30 2007, 16:26:42) 
[GCC 3.4.6] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from math import *
>>> 2*sqrt(2)/pi
0.90031631615710617
>>> atan(2*sqrt(2)/pi)
0.73298983460979572
>>> degrees(atan(2*sqrt(2)/pi))
41.997223949133534	#the angle the edge of the Great Pyramid makes with its base

[193]

Yes. There is only one pyramid possible.

[194]

Informal proof: If the angle between the edge and the base is fixed, then the height at the peak of the pyramid is determined. This fixes the triangle that makes the face of the pyramid.

[195]

You will have to draw in the right triangle that includes the angle "A". You know the length of the opposite side. What is the length of the adjacent side?

[196]

Right triangle added to elevation diagram. What is the length of the adjacent side?

-----------                 x
| .      .|                /|\     ^
|   .  .  |               / | \    |
|    x    |l=h*pi/2      /  |  \   h
|  .   .  |             /   |   \  |
|.       .|            / A) |    \ v
-----------            -----------
a         b            a         b

[197]

    
| .         
|   .          
|    x      
|  . | .        
|.   |   .  
----------- 
a         b  

length half diagonal = sqrt(0.5)*h*pi/2
all angles are 90 or 45 deg
height a-b to x is same as half a-b

by Pythagorus
2*(height a-b to x)^2 = (sqrt(0.5)*h*pi/2)^2
height a-b to x = sqrt(0.5)*h*pi/(2*sqrt(2))
height a-b to x = h*pi/4

[198]

The adjacent side is half the length of the side of the pyramid, i.e. adjacent=h*pi/4.

[199]

tan(A)=h/(h*pi/4)
      =4/pi
.
.
>>> degrees(atan(4/pi))
51.853974012777449	#angle between face and base

[200]

angle edge-to-base:
diagonal=sqrt(2)*side
	=sqrt(2)*l
half_diagonal
	=sqrt(2)*l/2
	=sqrt(0.5)*l

tan(edge-to-base)=h/half_diagonal
      =h/(sqrt(0.5)*l) 
      =sqrt(2)*h/l 

angle face-to-base:
tan(face-to-base)=h/(l/2)
	      =2*h/l

tan(face-to-base)/tan(edge-to-base) = sqrt(2)

[201]

#! /usr/bin/python
#
# graph_pyramid_angles.py
#
# Imhotep (C) 2008, imhotep (at) Saqqara (dot) net. Released under GPL v3
# generates graph of angles at edge of pyramid, against angle of face.
# uses PIL library.

#from math import sqrt
from math import *
import os, sys
from PIL import Image, ImageDraw, ImageFont, ImageOps

#fix coordinate system
#
#origin of diagram is at top left
#there appears to be no translate function ;-\
#strings are right (not center) justified
#axes origin is at 50,450 (bottom left)
#x_origin=50
#y_origin=450
x_origin=75
y_origin=450
#unit=400	#400 pixels = 1 in cartesian coords, suitable for box dimension 1x1
unit=4		#4 pixels = 100 in cartesian coords, suitable for box dimension 100x100

#change coordinate system.
#cartesian to pil coords
def x2pil(x):
	result=x_origin +x*unit 
	return result

def y2pil(y):
	result=y_origin -y*unit 
	return result

def draw_axes():
	#axes
	axes_origin=(x2pil(xmin), y2pil(ymin))
	
	x_axes=(axes_origin,x2pil(xmax),y2pil(ymin))
	draw.line(x_axes,fill="black")
	y_axes=(axes_origin,x2pil(ymin),y2pil(ymax))
	draw.line(y_axes,fill="black")

	#axes: numerical label 
	#x_axes
	color="#000000"
	x_axes_label_position_0=(x2pil(xmin-2), y2pil(ymin-2))
	draw.text(x_axes_label_position_0, str(xmin), font=label_font, fill=color)
	x_axes_label_position_1=(x2pil(xmax-2), y2pil(ymin-2))
	draw.text(x_axes_label_position_1, str(xmax), font=label_font, fill=color)

	#y_axes
	y_ayes_label_position_0=(x2pil(xmin-7), y2pil(ymin+3))
	draw.text(y_ayes_label_position_0, str(ymin), font=label_font, fill=color)
	y_ayes_label_position_1=(x2pil(xmin-7), y2pil(ymax+3))
	draw.text(y_ayes_label_position_1, str(ymax), font=label_font, fill=color)

def label_graph():
	color="#000000"
	#graph label
	label_position=(x2pil(xmin-10), y2pil(ymax+15))
	label="Pyramid: angle at edge, base:"
	draw.text(label_position, label, font=label_font, fill=color)

	#x_axes label
	label_position=(x2pil(xmin+10), y2pil(ymin-2))
	label="angle: edge to base"
	draw.text(label_position, label, font=label_font, fill=color)

	#rotated text
	#from http://mail.python.org/pipermail/image-sig/2008-August/005145.html
	#x=-10
	#y=ymax+5
	x=-7
	y=ymax-5
	for c in "angle: face to base":
		draw.text ((x2pil(x),y2pil(y)), c, font=label_font, fill=color)
		#y -= 5
		y -= 4
		

def draw_fine_grid():
	#draw fine grid, spacing = 1 degree
	start=xmin
	end=xmax
	step=1	#in user units
	color="#00ff00"
	for x in xrange(start,end+1,step):
		h = ymax
		#vertical line
		line=(x2pil(x),y2pil(ymin),x2pil(x),y2pil(ymax))
		draw.line(line,fill=color)
		#horizontal line
		line=(x2pil(xmin),y2pil(x),x2pil(xmax),y2pil(x))
		draw.line(line,fill=color)

def draw_coarse_grid():
	#draw coarse grid, spacing = 10deg
	start=xmin
	end=xmax
	step=10	#user units
	color="#000000"
	for x in xrange(start,end+1,step):
		h = ymax
		#vertical line
		line=(x2pil(x),y2pil(ymin),x2pil(x),y2pil(ymax))
		draw.line(line,fill=color)
		#horizontal line
		line=(x2pil(xmin),y2pil(x),x2pil(xmax),y2pil(x))
		draw.line(line,fill=color)

def draw_line():
	step=10	#in user units
	#color="#0000FF"
	color="#FF0000"
	for x in xrange(xmin,xmax,step):
		#need atan(sqrt(2)*tan(x)
		y0 = degrees(atan(sqrt(2)*tan(radians(x))))
		y1 = degrees(atan(sqrt(2)*tan(radians(x+step))))
		line=(x2pil(x),y2pil(y0),x2pil(x+step),y2pil(y1))
		draw.line(line,fill=color)
	#draw reference line
	line=(x2pil(xmin),y2pil(ymin),x2pil(xmax),y2pil(ymax))
	draw.line(line,fill="#777777")
#---------------------
size=(500,500)
mode="RGBA"
xmin=0
ymin=0
xmax=90
ymax=90

#fonts
#print sys.path
label_font = ImageFont.load("/usr/lib/python2.4/site-packages/PIL/courR18.pil")
#label_font = ImageFont.load("PIL/courR18.pil")

im=Image.new (mode,size,"white")
draw=ImageDraw.Draw(im)

draw_axes()

label_graph()

draw_fine_grid()

draw_coarse_grid()

draw_line()

im.show()
im.save("graph_pyramid_angles.png")

# graph_pyramid_angles---------------------------

[202]

pyramid formulae

edge-to-base:
tan(edge-to-base)=h/half_diagonal
      =h/(sqrt(0.5)*l) 
      =sqrt(2)*h/l 
     
angle face-to-base:
tan(face-to-base)=h/(l/2)
	=2*h/l

Calculations for the Transamerica Pyramid:

edge-to-base:

pip:/src/da/python_class# python
Python 2.4.4 (#2, Mar 30 2007, 16:26:42) 
[GCC 3.4.6] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from math import *
>>> sqrt(2)*853/174	#tan(edge-to-base)
6.9328975212887949
>>> atan(sqrt(2)*853/174)
1.4275444962018089	#angle edge-to-base in radians
>>> degrees(atan(sqrt(2)*853/174))
81.792274699493021	#angle edge-to-base in degrees

face-to-base:
>>> 2*853/174  		#substituting value of 2 for sqrt(2) in edge-to-base formula)
9
.
.
>>> degrees(atan(2*853/174))
83.659808254090095	#angle face-to-base in degrees 
>>> 


[203]

AustinTek homepage | Linux Virtual Server Links | AZ_PROJ map server |