Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

primer_on_scientific_programming_with_python

.pdf
Скачиваний:
2
Добавлен:
07.04.2024
Размер:
11.31 Mб
Скачать

3.6 Summary

157

 

 

program really does the right things. Running the program yields the output

Iteration 1: interval=[0, 5] Iteration 2: interval=[0, 2.5] Iteration 3: interval=[1.25, 2.5] Iteration 4: interval=[1.25, 1.875]

...

Iteration 19: interval=[1.5, 1.50002] Iteration 20: interval=[1.5, 1.50001]

The root is 1.50000572205 found in 20 iterations f(1.50001)=1.14441e-05

It seems that the implementation works. Further checks should include hand calculations for the first (say) three iterations and comparison of the results with the program.

Making a Function. The previous implementation of the bisection algorithm is fine for many purposes. To solve a new problem f (x) = 0 it is just necessary to change the f(x) function in the program. However, if we encounter solving f (x) = 0 in another program in another context, we must put the bisection algorithm into that program in the right place. This is simple in practice, but it requires some careful work, and it is easy to make errors. The task of solving f (x) = 0 by the bisection algorithm is much simpler and safer if we have that algorithm available as a function in a module. Then we can just import the function and call it. This requires a minimum of writing in later programs.

When you have a “flat” program as shown above, without basic steps in the program collected in functions, you should always consider dividing the code into functions. The reason is that parts of the program will be much easier to reuse in other programs. You save coding, and that is a good rule! A program with functions is also easier to understand, because statements are collected into logical, separate units, which is another good rule! In a mathematical context, functions are particularly important since they naturally split the code into general algorithms (like the bisection algorithm) and a problem-specific part (like a special choice of f (x)).

Shu ing statements in a program around to form a new and better designed version of the program is called refactoring. We shall now refactor the bisection_v1.py program by putting the statements in the bisection algorithm in a function bisection. This function naturally takes f (x), a, b, and as parameters and returns the found root, perhaps together with the number of iterations required:

def bisection(f, a, b, eps): fa = f(a)

if fa*f(b) > 0: return None, 0

i = 0 # iteration counter while b-a < eps:

i += 1

m = (a + b)/2.0 fm = f(m)

158 3 Input Data and Error Handling

if fa*fm <= 0:

b = m # root is in left half of [a,b] else:

a = m # root is in right half of [a,b] fa = fm

return m, i

After this function we can have a test program:

def f(x):

return 2*x - 3 # one root x=1.5

eps = 1E-5 a, b = 0, 10

x, iter = bisection(f, a, b, eps) if x is None:

print ’f(x) does not change sign in [%g,%g].’ % (a, b) else:

print ’The root is’, x, ’found in’, iter, ’iterations’ print ’f(%g)=%g’ % (x, f(x))

The complete code is found in file bisection_v2.py.

Making a Module. A motivating factor for implementing the bisection algorithm as a function bisection was that we could import this function in other programs to solve f (x) = 0 equations. However, if we do an import

from bisection_v2 import bisection

the import statement will run the main program in bisection_v2.py. We do not want to solve a particular f (x) = 0 example when we do an import of the bisection function! Therefore, we must put the main program in a test block (see Chapter 3.5.2). Even better is to collect the statements in the test program in a function and just call this function from the test block:

def _test(): def f(x):

return 2*x - 3 # one root x=1.5

eps = 1E-5 a, b = 0, 10

x, iter = bisection(f, a, b, eps) if x is None:

print ’f(x) does not change sign in [%g,%g].’ % (a, b) else:

print ’The root is’, x, ’found in’, iter, ’iterations’ print ’f(%g)=%g’ % (x, f(x))

if __name__ == ’__main__’: _test()

The complete module with the bisection function, the _test function, and the test block is found in the file bisection.py.

Using the Module. Suppose you want to solve x = sin x using the bisection module. What do you have to do? First, you reformulate

3.6 Summary

159

 

 

the equation as f (x) = 0, i.e., x − sin x = 0 so that you identify f (x) = x − sin x. Second, you make a file, say x_eq_sinx.py, where you import the bisection function, define the f (x) function, and call bisection:

from bisection import bisection from math import sin

def f(x):

return x - sin(x)

root, iter = bisection(f, -2, 2, 1E-6) print root

A Flexible Program for Solving f (x) = 0. The previous program hardcodes the input data f (x), a, b, and to the bisection method for a specific equation. As we have pointed out in this chapter, a better solution is to let the user provide input data while the program is running. This approach avoids editing the program when a new equation needs to be solved (and as you remember, any change in a program has the danger of introducing new errors). We therefore set out to create a program that reads f (x), a, b, and from the command-line. The expression for f (x) is given as a text and turned into a Python function with aid of the StringFunction object from Chapter 3.1.4. The other parameters – a, b, and – can be read directly from the command line, but it can be handy to allow the user not to specify and provide a default value in the program instead.

The ideas above can be realized as follows in a new, general program for solving f (x) = 0 equations. The program is called bisection_solver.py:

import sys

usage = ’%s f-formula a b [epsilon]’ % sys.argv[0] try:

f_formula = sys.argv[1] a = float(sys.argv[2]) b = float(sys.argv[3])

except IndexError:

print usage; sys.exit(1)

try: # is epsilon given on the command-line? epsilon = float(sys.argv[4])

except IndexError:

epsilon = 1E-6 # default value

from scitools.StringFunction import StringFunction from math import * # might be needed for f_formula f = StringFunction(f_formula)

from bisection import bisection

root, iter = bisection(f, a, b, epsilon) if root == None:

print ’The interval [%g, %g] does not contain a root’ % (a, b) sys.exit(1)

print ’Found root %g\nof %s = 0 in [%g, %g] in %d iterations’ % \ (root, f_formula, a, b, iter)

160

3 Input Data and Error Handling

 

 

Let us solve

1.x = tanh x with start interval [−10, 10] and default precision ( = 10−6),

2.x5 = tanh(x5) with start interval [−10, 10] and default precision.

Both equations have one root x = 0.

Terminal

bisection_solver.py "x-tanh(x)" -10 10 Found root -5.96046e-07

of x-tanh(x) = 0 in [-10, 10] in 25 iterations

bisection_solver.py "x**5-tanh(x**5)" -10 10 Found root -0.0266892

of x**5-tanh(x**5) = 0 in [-10, 10] in 25 iterations

These results look strange. In both cases we halve the start interval [−10, 10] 25 times, but in the second case we end up with a much less accurate root although the value of is the same. A closer inspection of what goes on in the bisection algorithm reveals that the inaccuracy is caused by round-o errors. As a, b, m → 0, raising a small number to the fifth power in the expression for f (x) yields a much smaller result. Subtracting a very small number tanh x5 from another very small number x5 may result in a small number with wrong sign, and the sign of f is essential in the bisection algorithm. We encourage the reader to graphically inspect this behavior by running these two examples with the bisection_plot.py program using a smaller interval [−1, 1] to better see what is going on. The command-line arguments for the bisection_plot.py program are ’x-tanh(x)’ -1 1 and

’x**5-tanh(x**5)’ -1 1. The very flat area, in the latter case, where f (x) ≈ 0 for x [−1/2, 1/2] illustrates well that it is di cult to locate an exact root.

3.7 Exercises

Exercise 3.1. Make an interactive program.

Make a program that (i) asks the user for a temperature in Fahrenheit and reads the number; (ii) computes the correspodning temperature in Celsius degrees; and (iii) prints out the temperature in the Celsius scale. Name of program file: f2c_qa.py.

Exercise 3.2. Read from the command line in Exer. 3.1.

Modify the program from Exercise 3.1 such that the Fahrenheit temperature is read from the command line. Name of program file: f2c_cml.py.

3.7 Exercises

161

 

 

Exercise 3.3. Use exceptions in Exer. 3.2.

Extend the program from Exercise 3.2 with a try-except block to handle the potential error that the Fahrenheit temperature is missing on the command line. Name of program file: f2c_cml.py.

Exercise 3.4. Read input from the keyboard.

Make a program that asks the user for an integer, a real number, a list, a tuple, and a string. Use eval to convert the input string to a list or tuple. Name of program file: objects_qa1.py.

Exercise 3.5. Read input from the command line.

Let a program store the result of applying the eval function to the first command-line argument. Print out the resulting object and its type (use type from Chapter 1.5.2). Run the program with di erent input: an integer, a real number, a list, and a tuple. Then try the string "this is a string" as a command-line argument. Why does this string cause problems and what is the remedy? Name of program file: objects_cml.py.

Exercise 3.6. Prompt the user for input to the formula (1.1).

Consider the simplest program for evaluting (1.1):

v0 = 3; g = 9.81; t = 0.6 y = v0*t - 0.5*g*t**2 print y

Modify this code so that the program asks the user questions t=? and v0=?, and then gets t and v0 from the user’s input through the keyboard. Name of program file: ball_qa.py.

Exercise 3.7. Read command line input for the formula (1.1).

Modify the program listed in Exercise 3.6 such that v0 and t are read from the command line. Name of program file: ball_cml.py.

Exercise 3.8. Make the program from Exer. 3.7 safer.

The program from Exercise 3.7 reads input from the command line. Extend that program with exception handling such that missing command-line arguments are detected. In the except IndexError block, use the raw_input function to ask the user for missing input data. Name of program file: ball_cml_qa.py.

Exercise 3.9. Test more in the program from Exer. 3.7.

Test if the t value read in the program from Exercise 3.7 lies be-

tween 0 and

2v0

. If not, print a message and abort execution. Name of

g

 

 

 

program file: ball_cml_errorcheck.py.

Exercise 3.10. Raise an exception in Exer. 3.9.

Instead of printing an error message and aborting the program explicitly, raise a ValueError exception in the if test on legal t values in

162

3

Input Data and Error Handling

 

 

 

the program from Exercise 3.9. The exception message should contain

 

the legal interval for t. Name of program file: ball_cml_ValueError.py.

 

 

 

 

Exercise 3.11. Look up calendar functionality.

 

The purpose of this exercise is to make a program which takes a

 

date, consisting of year (4 digits), month (2 digits), and day (1-31) on

 

the command line and prints the corresponding name of the weekday

 

(Monday, Tuesday, etc.). Python has a module calendar, which you

 

must look up in the Python Library Reference (see Chapter 2.4.3), for

 

calculating the weekday of a date. Name of program file: weekday.py.

 

 

 

 

Exercise 3.12. Use the StringFunction tool.

 

 

Make the program user_formula.py from Chapter 3.1.3 shorter by

 

using the convenient StringFunction tool from Chapter 3.1.4. Name of

 

program file: user_formula2.py.

 

 

Exercise 3.13. Extend a program from Ch. 3.2.1.

 

How can you modify the add_cml.py program from the end of Chap-

 

ter 3.1.2 such that it accepts input like sqrt(2) and sin(1.2)? In this

case the output should be

<type ’float’> + <type ’float’> becomes <type ’float’>

with value 2.34625264834

(Hint: Mathematical functions, such as sqrt and sin, must be defined in the program before using eval. Furthermore, Unix (bash) does not like the parentheses on the command line so you need to put quotes around the command-line arguments.) Name of program file: add2.py.

Exercise 3.14. Why we test for specific exception types.

The simplest way of writing a try-except block is to test for any exception, for example,

try:

C = float(sys.arg[1]) except:

print ’C must be provided as command-line argument’ sys.exit(1)

Write the above statements in a program and test the program. What is the problem?

The fact that a user can forget to supply a command-line argument when running the program was the original reason for using a try block. Find out what kind of exception that is relevant for this error and test for this specific exception and re-run the program. What is the problem now? Correct the program. Name of program file: cml_exception.py.

3.7 Exercises

163

 

 

Exercise 3.15. Make a simple module.

Make six conversion functions between temperatures in Celsius, Kelvin, and Fahrenheit: C2F, F2C, C2K, K2C, F2K, and K2F. Collect these functions in a module convert_temp. Make some sample calls to these functions from an interactive Python shell. Name of program file: convert_temp.py.

Exercise 3.16. Make a useful main program for Exer. 3.15.

Extend the module made in Exercise 3.15 with a main program in the test block. This main program should read the first commandline argument as a numerical value of a temperature and the second argument as a temperature scale: C, K, or F. Write out the temperature in the other two scales. For example, if 21.3 C is given on the command line, the output should be 70.34 F 294.45 K. Name of program file: convert_temp2.py.

Exercise 3.17. Make a module in Exer. 2.39.

Collect the functions in the program from Exercise 2.39 in a separate file such that this file becomes a module. Put the statements making the table (i.e., the main program) in a separate function table(t, T, n_values), and call this function only if the module file is run as a program (i.e., include a test block, see Chapter 3.5.2). Name of program file: compute_sum_S_module.py.

Exercise 3.18. Extend the module from Exer. 3.17.

Extend the program from Exercise 3.17 such that t, T , and n are read from the command line. The extended program should import the table function from the module compute_sum_S_module and not copy any code from the module file or the program file from Exercise 2.39. Name of program file: compute_sum_S_cml.py.

Exercise 3.19. Use options and values in Exer. 3.18.

Let the input to the program in Exercise 3.18 be option-value pairs of the type -t, -T, and -n, with sensible default values for these quantities set in the program. Apply the getopt module to read the command-line arguments. Name of program file: compute_sum_S_cml_getopt.py.

Exercise 3.20. Use optparse in the program from Ch. 3.2.4.

Python has a module optparse, which is an alternative to getopt for reading -option value pairs. Read about optparse in the o - cial Python documentation, either the Python Library Reference or the Global Module Index. Figure out how to apply optparse to the location.py program from Chapter 3.2.4 and modify the program to make use of optparse instead of getopt. Name of program file: location_optparse.py.

Exercise 3.21. Compute the distance it takes to stop a car.

A car driver, driving at velocity v0, suddenly puts on the brake. What braking distance d is needed to stop the car? One can derive,

164 3 Input Data and Error Handling

from basic physics, that

 

1 v2

 

 

d =

 

 

0

.

(3.7)

2 μg

 

 

 

Make a program for computing d in (3.7) when the initial car velocity v0 and the friction coe cient μ are given on the command line. Run the program for two cases: v0 = 120 and v0 = 50 km/h, both with μ = 0.3 (μ is dimensionless). (Remember to convert the velocity from km/h to m/s before inserting the value in the formula!) Name of program file: stopping_length.py.

Exercise 3.22. Check if mathematical rules hold on a computer.

Because of round-o errors, it could happen that a mathematical rule like (ab)3 = a3b3 does not hold (exactly) on a computer. The idea of this exercise is to check such rules for a large number of random numbers. We can make random numbers using the random module in Python:

import random

a = random.uniform(A, B) b = random.uniform(A, B)

Here, a and b will be random numbers which are always larger than or equal to A and smaller than B.

Make a program that reads the number of tests to be performed from the command line. Set A and B to fixed values (say -100 and 100). Perform the test in a loop. Inside the loop, draw random numbers a and b and test if the two mathematical expressions (a*b)**3 and a**3*b**3 are equivalent. Count the number of failures of equivalence and write

out the percentage of failures at the end of the program.

 

Duplicate the code segment

outlined

above

to also

com-

pare the expressions a/b and

1/(b/a).

Name of

program

file:

math_rules_failures.py.

 

 

 

 

Exercise 3.23. Improve input to the program in Exer. 3.22.

The purpose of this exercise is to extend the program from Exercise 3.22 to handle a large number of mathematical rules. Make a function equal(expr1, expr2, A, B, n=500) which tests if the mathematical expressions expr1 and expr2, given as strings and involving numbers a and b, are exactly equal (eval(expr1) == eval(expr2)) for n random choices of numbers a and b in the interval between A and B. Return the percentage of failures. Make a module with the equal function and a test block which feeds the equal function with arguments read from the command line. Run the module file as a program to test the two rules from Exercise 3.22. Also test the rules ea+b = aaeb and ln ab = b ln a (take a from math import * in the module file so that mathematical functions like exp and log are defined). Name of program file: math_rules_failures_cml.py.

166 3 Input Data and Error Handling

This formula (3.8) is called the binomial distribution. The expression x! is the factorial of x as defined in Exercise 2.33. Implement (3.8) in a function binomial(x, n, p). Make a module containing this binomial function. Include a test block at the end of the module file. Name of program file: binomial_distribution.py.

Exercise 3.26. Apply the binomial distribution.

Use the module from Exercise 3.25 to make a program for solving the problems below.

1.What is the probability of getting two heads when flipping a coin five times?

This probability corresponds to n = 5 events, where the success of an event means getting head, which has probability p = 1/2, and we look for x = 2 successes.

2.What is the probability of getting four ones in a row when throwing a die?

This probability corresponds to n = 4 events, success is getting one and has probability p = 1/6, and we look for x = 4 successful events.

3.Suppose cross country skiers typically experience one ski break in one out of 120 competitions. Hence, the probability of breaking a ski can be set to p = 1/120. What is the probability b that a skier will experience a ski break during five competitions in a world championship?

This question is a bit more demanding than the other two. We are looking for the probability of 1, 2, 3, 4 or 5 ski breaks, so it is simpler to ask for the probability c of not breaking a ski, and then compute b = 1 −c. Define “success” as breaking a ski. We then look for x = 0 successes out of n = 5 trials, with p = 1/120 for each trial. Compute b.

Name of program file: binomial_problems.py.

Exercise 3.27. Compute probabilities with the Poisson distribution.

Suppose that over a period of tm time units, a particular uncertain event happens (on average) νtm times. The probability that there will be x such events in a time period t is approximately given by the

formula

(νt)x

 

 

 

P (x, t, ν) =

 

e−νt .

(3.9)

 

 

x!

 

This formula is known as the Poisson distribution12. An important assumption is that all events are independent of each other and that the probability of experiencing an event does not change significantly over time.

12It can be shown that (3.9) arises from (3.8) when the probability p of experiencing the event in a small time interval t/n is p = νt/n and we let n → ∞.