Tutorial 7 Topics Numerical analysis in Java Root Finding Integration Tutorial Exercises Design Exercises Numerical analysis in Java Numerical Analysis is a branch of applied mathematics which devises and evaluates techniques for employing computers to solve numerical problems, and to study their convergence and errors. Consider writing a function that finds a root of a function, f(x) = 0, in some interval [a,b]. This root finding method should have the function f(x) as an argument. In Java, since (almost) everything has to be an object, this is done by wrapping the function you want to pass in an object. You can then pass the object as an argument to the method which finds a root. This wrapping is usually done by declaring an interface that describes an object which contains a function. An interface is like a model that specifies one of more capabilities (methods) that must be defined in every class that "implements" it. Only function prototypes can be defined in interfaces (the bodies of these methods are then defined in the classes that implement the particular interface). For instance, let's declare the following interface: interface MathFunction { public double func(double x); } We can then use this interface to declare a class with a particular function: class Function1 implements MathFunction { public double func(double x) { return x * x - 2; //the definition for a particular function, //here, for instance, represent f(x) = x*x-2; } } So, now, we would define our root finding function as follows: public class RootFinder { public static double rootFindingMethod( MathFunction mathf, ...) { // finding the root for a particular function ... } } ... and we would call the function as follows with Function1: double rootResult = RootFinder.rootFindingMethod(new Function1(), ...); For instance, the following code will find the root for Function1( f(x) = x2-2) by using the bisect method defined in the next section (suppose the bisect method is defined in the class RootFinder): // main() method in class RootFinder public static void main(String[] args) { double root= RootFinder.bisect(new Function1(), -1.0, 2.0, 0.0001); System.out.println("Root: " + root); System.exit(0); } } Root finding There are four "elementary" methods for root finding. Of these, bisection and Newton's method can be used to solve actual problems in some circumstances. Secant and regula falsi are only of pedagogical value. See "Numerical Recipes in C" for discussions of these and other methods if you actually need to find roots; the first few pages of text on each method are relatively readable and illuminating. For the bisection, secant and regula falsi methods, we give the reasoning behind each and a snippet of pseudo-code that implements it. This code is illustrative only; it is not robust nor should it be used for actual problems. Bisection Start with an interval known to bracket a root. Divide it in half, reducing the interval to a half known to contain a root. Do this until the interval is small enough public static double bisect( MathFunction f, double x1, double x2, double epsilon) { double m; for (m= (x1+x2)/2.0; Math.abs(x1-x2)> epsilon; m= (x1+x2)/2.0) if (f.func(x1)*f.func(m) <= 0.0) x2= m; // Use left subinterval else x1= m; // Use right subinterval return m; } Secant One of the root finding methods, the Secant method, begins with an interval known to contain a root. Call the endpoints of this interval x1 and x2. The method works by fitting a straight line between the two current points (x1, f(x1)) and (x2, f(x2)) and finding the point x* where that line intersects the x axis. The end point x1 is reset to x2's value. The point x* becomes the new endpoint (x2). This procedure continues until either the interval is very small, or we find a root of the function. The following is the code for implementing the method. start with x1 and x2 fit a straight line between f(x1) and f(x2) find x*, the root of this straight line if x* is close to x2, it returns the value as the root of the original equation If not, it resets the value of x1 to x2 and x2 to x* and tries the method again. public static double secant(MathFunction f, double x1, double x2, double epsilon) { double root = x1; double y1, y2; while (Math.abs(x2 - x1) > epsilon) { y1 = f.func(x1); y2 = f.func(x2); root = x1-y1*(x2-x1)/(y2-y1); x1 = x2; x2 = root; } return root; } Regula Falsi The Regula Falsi method is almost the same as the Secant method. The difference is when the point x* becomes one of the endpoints of the next interval, the other endpoint is whichever of x1 and x2 ensures that the new interval still contains a root of the function, i.e., the Regula Falsi mehtod chooses the 'previous' point to keep the zero bracketed instead of always using just the last two points. start with x1 and x2 fit a straight line between f(x1) and f(x2) find x*, the root of this straight line if f(x*) is close to 0, it returns the value x* as the root of the original equation If not, it resets (x1, x2) to (x1, x*) or (x*, x2) (the new interval that contains a root of the function) and tries the method again. public static double regulaFalsi(MathFunction f, double x1, double x2, double epsilon) { double y1, y2, root, yroot; do { y1 = f.func(x1); y2 = f.func(x2); root = x1-y1*(x2-x1)/(y2-y1); yroot = f.func(root); if (yroot*y2 < 0.0) x1=root; else x2=root; } while (Math.abs(yroot)>epsilon); return root; } Newton Start at some point x on the x-axis fit a line tangent to f(x) at x solve for the point y where the tangent intersects the x-axis do this until |x-y| < epsilon, then return y The code can be found in the lecture notes. Integration Numerical integration is used in the calculation of definite integrals. Elementary methods for numerical integration are based on the summation of small areas (slices) under a function's curve and between two limits x1and x2. Depending on the way the area of each segment is computed, we have the following elementary or pedagogical methods: Rectangular rule The method is based on the areas of rectangles. The area of each segment is computed as: or where and A is the area of segment. Trapezoidal rule This method is as simple as the rectangular rule but is often more accurate. It is based on the areas of trapezoids. The area of each segment A is computed as: Simpson's rule Simpson's rule uses 3 points to approximate the function with a second order polynomial. The area of each segment is computed as: Simpson's rule is a commonly taught method for numerical integration of definite integrals. It approximates the function by fitting parabolas to each segment, then summing the areas under each segment to calculate the area under the curve. The following code implements an elementary version of Simpson's rule: // Simpson's Rule public static double simpson(MathFunction f, double a, double b, int nPanels) { double answer = 0; double x = a; double h = (b - a) / nPanels; for(int i=1; i <= nPanels; i++) { answer += (f.func(x) + 4 * f.func(x + h / 2) + f.func(x + h)) / 6; x = a + i * h; } return h * answer; } To solve real problems, you should use extended Simpson's rule, which is the simplest usable version. The extended Simpson's method is covered in the Lecture Notes 20. Tutorial Exercises 1. Given two functions, f(x) and g(x), how can we determine the value of x where f(x) = g(x)? 2. How do we determine if points x1 and x2 bracket the root of function f(x)? 3. When are the root finding methods used? When are numerical integration methods used? 4. Trace the bisection method for the function f(x) = x2 - 5x +4 starting from a=0 and b=3 for a couple of iterations. 5. Why do we use the derivative of f(x) when we're using Newton's method to find the root of f(x)? Design Exercises Design the following three classes: RootFinder, Integration, and NumericalTest. RootFinder will implement elementary versions of the three root finding methods (Bisect, Secant, and Regula Falsi from the tutorial notes, not the lecture notes). Integration will implement elementary versions of the three integration methods (Rectangular, Trapezoidal, and Simpson). NumericalTest will have a main method to test the three root-finding methods by finding the root for: f1(x) = x5 + 4x3 + 7x - 10; (finding the root in the range from -1.0 to 2.0, using epsilon= 0.0001) and test the three integration methods by integrating for the function: f2(x) = x6 + 7x5 + 3x4 + x2 - 5x +4; (integrating from -5.0 to 8.0, using n = 1000) Execute your program and compare the result of these methods. Hint: You will have to create two classes to represent f1(x) and f2(x). Create these classes by implementing the MathFunction interface.