Chapter 6. Nonlinear Equations

  • 6.1 Introduction

  • 6.2 Existence of Solutions

  • 6.3 Bisection Method

  • 6.4 False Position Method

  • 6.5 Newton—Raphson Method

  • 6.6 Secant Method

  • 6.7 Fixed-Point Iteration Method

  • 6.8 Visual Solution: Code6

  • 6.9 Summary

    • Numerical Exercises

    • Programming Challenges

INTRODUCTION

As described in the previous chapter, a nonlinear equation is an equation that has one or more nonlinear terms in its expression, or an equation that is not in the form of Equation (5.1). In its general form, a nonlinear equation involving n variables, x1, x2, x3,...,xn, can be expressed as

Equation 6.1. 

INTRODUCTION

Some examples of nonlinear equations are given below:

3 — 3x2 + x3 = −2,

nonlinear because of the presence of nonlinear terms x2 and x 3.

x + sin y = −1,

nonlinear because of the presence of nonlinear term sin y.

xy = −1

nonlinear because the sum of indices in the variable term is 2.

INTRODUCTION

nonlinear because variable x is in the denominator.

Finding the root(s) of a nonlinear equation is one of the most fundamental problems involving nonlinear equations. The problem is stated as follows:

Given a function f(x) where f(x)= 0 exists, find the value of x.

Alternatively, the problem is also called finding the zeros of a function. Graphically, this problem can be described as finding one or more points along the x -axis where the curve f (x) crosses, provided the value(s) exist. Very often, f (x) does not cross the x -axis. In this case, f (x) is said to have no real root.

EXISTENCE OF SOLUTIONS

Several theorems have been documented to help in finding the zeros of a function.

Theorem 6.1. Intermediate-Value Theorem. Given a function f (x) continuous in [a, b] and a number C, where f(a)Cf(b), then a number p ɛ (a,b) exists such that f(c) = C.

Theorem 6.2. Mean-Value Theorem. A function f(x) that is continuous in [a, b] is said to have at least one root in this interval if f(a) f(b) < 0.

The intermediate-value theorem and the mean-value theorem guarantee at least one root exists in the interval [a, b] for f(x) if f(a)f(b) < 0. However, it does not assume the uniqueness of the solution. f(a)f(b) < 0 suggests one of the terms in f(a) or f(b) is positive and the other is negative. This suggestion implies the positive term lies above the x -axis, whereas the negative term is located below the axis. Therefore, the curve must cross the x -axis at least once along the path from (0, f(a)) to (0, f(b)). This is illustrated in Figure 6.1.

In this chapter, we will concentrate on the case of finding a unique root for f(x) given the solution exists. We will discuss five methods based on iterations, namely, the bisection, false position, Newton-Raphson, secant, and fixed-point methods. In each method, we will emphasize on the construction of C++ code in solving the problem and on the development of its Windows interface. All these methods are summarized in Table 6.1 for their brief description.

Mean-value theorem illustration of curves with one root (left) and three roots (right).

Figure 6.1. Mean-value theorem illustration of curves with one root (left) and three roots (right).

Table 6.1. Summary of the iterative methods

Method

Type

Summary of Method

Bisection

Closed interval

Update based on bisecting the middle point in the interval.

False position

Closed interval

Update based on the secant line between the end-points of the intervals.

Newton—Raphson

Open interval

Update based on the tangent line intersection with the x -axis.

Secant

Open interval

Update based on the secant line intersection with the x -axis.

Fixed-point iteration

Open interval

Update based on the formulation x = g (x) from f(x) = 0.

An iterative technique for finding the zeros of an equation can be classified as either the closed or the open interval approach. A closed interval means the final solution is confined to within the given initial interval, whereas an open interval does not limit the solution to be within this range.

In the closed interval approach, an interval x ɛ [a0,b 0] that contains at least one root is identified using the mean-value theorem. The two initial values, a0 and b0, serve as the left and right points in the interval. The size of this initial interval is reduced gradually through successive iterations to[a1, b1], [a2, b2], and so on until the solution converges to an acceptable value x* based on a stopping condition. This final value is then the final solution to the problem. The beauty of the closed interval approach is convergence to x* is guaranteed through the mean-value theorem. Some well-known methods in the closed interval approach are the bisection and false position methods.

In the open interval approach, an initial value x0 is selected as the starting value for x in the iteration. This value is a guess value that should not be far from the solution. A good guess for this value is either a0 or b0 in the interval x ɛ [a0, b0] using the mean-value theorem. The interval for the convergence is said to be open in this approach as the generated values are not bound to be within a specified range. Successive iterations are then performed on the initial value, which eventually leads to convergence to its solution after a stopping condition has been reached. The Newton-Raphson, secant and fixed-point iteration methods are some of the most common methods in the open interval approach.

BISECTION METHOD

The bisection method is based on a closed interval that produces a solution by reducing the interval size successively into a tolerable value through a series of iterations. The iterations start at interval x ε [a0, b0] whose end points x = a0 and x = b0 are any values that comply with the mean-value theorem requirement, or f (a0) f (b0) < 0.

Bisection method.

Figure 6.2. Bisection method.

Figure 6.2 illustrates the bisection method showing the shrinking intervals after four successive iterations. At each iteration i, the size of the interval x ɛ [ai, bi] from the end points ai and bi is reduced into half by updating one of the two end points with the value of the interval midpoint, given by

Equation 6.2. 

Bisection method.

The update is performed by looking at the sign of f (ai)f (ci) to quickly determine the rough location of the root. A value of f (ai) f (ci) < 0 suggests the root lies in x ɛ [ai, ci], definitely not in [ci, bi]. Therefore, the end point bi +1 is updated with the value of ci, whereas ai +1 takes on ai, or bi +1 = ci and ai +1 = ai, respectively. In a similar note, f (ai)f (ci) > 0 suggests the root lies in x ε [ci, b,i] not in x ɛ [ai, ci]. In this case, ai +1 = ci and bi +1 = bi. In a unique case where f (ai)f (ci) = 0, the solution has been reached with x* = ci as the root of f(x).

The update on the end points of the intervals has the effect of narrowing down the search area to the stage where the difference between the end points is very small. An error given by e = |cici1| for i > 0 is computed at each iteration. This error value is compared with a presetsmall value ε to determine whether the iterations are continued or stopped. The iterations are continued if e > ε, which suggests convergence to the solution is not reached yet. Convergence is said to have been reached if e ≤ ε, and the whole operation is stopped immediately. The final value of ci obtained from the last iteration then becomes the desired solution.

Algorithm 6.1 summarizes the computational steps in the bisection method. It is important to set the maximum number of iterations max to be a reasonable number such as 20. Depending on the value ε, convergence to the solution is normally reached before this number.

Figure 6.3 shows a schematic flowchart of the bisection method. The iterations are performed as long as the computed error at each iteration, e = |cici1|, is less than ε. There are also other ways to determine the error, including e = |biai|, or e = |f (ci)|.

Example 6.1. Find the root of f(x) = x3x2 − 2 using the bisection method, given the initial values of a0 = 1 and b0 = 2 with iterations until |cici −1| < ε, where ε = 0.005.

Solution. A quick check confirms f(a0)f(b0) = f (1)f (2) < 0. Therefore, at least one root lies inside x ɛ [1, 2]. At i = 0, a0 = 1 and b0 = 2, and this produces c0 =

Bisection Method.

At i = 1, a1 = 1.5 and b1 = 2. This gives

Bisection Method.

Table 6.2 shows the results after eight iterations, which eventually leads to convergence when the error is smaller than ε. The final solution is x* = c7 = 1.699219, which is obtained at i = 7 when |c7c6| = 0.003906 < ε.

Schematic flowchart of the bisection method.

Figure 6.3. Schematic flowchart of the bisection method.

FALSE POSITION METHOD

The false position method is based on a linear interpolation of the two end points of the interval for approximating the root. The method starts with the end points a0 and b0 in the interval [a0,b 0], where f (a0) f (b0) < 0. The gradient of the secant line from (a0, f (a0)) to (c0, 0) is given by

Equation 6.3. 

FALSE POSITION METHOD

Table 6.2. Numerical results from Example 6.1

i

ai

bi

ci

f (ai)f (ci)

|cici−1|

0

1.000000

2.000000

1.500000

1.750000

 

1

1.500000

2.000000

1.750000

−0.259766

0.250000

2

1.500000

1.750000

1.625000

0.305908

0.125000

3

1.625000

1.750000

1.687500

0.014766

0.062500

4

1.687500

1.750000

1.718750

−0.005206

0.031250

5

1.687500

1.718750

1.703125

−0.001669

0.015625

6

1.687500

1.703125

1.695313

0.000068

0.007813

7

1.695313

1.703125

1.699219

−0.000030

0.003906

whereas the gradient from the straight line from (c0,0) to (b0,f (b0)) is

Equation 6.4. 

Numerical results from Example 6.1

Since the two lines above are colinear, or m1 = m2,

Equation 6.5. 

Numerical results from Example 6.1

Simplify the above equation, we get

Equation 6.6. 

Numerical results from Example 6.1

and this equation generalizes to

Equation 6.3. 

Numerical results from Example 6.1

Figure 6.4 illustrates the false position method. a0 and b0 are two guess values that make up the initial interval [a0, b0] that is derived from the condition in the mean-value theorem. A secant line is drawn from (a0, f (a0)) to (b0, f(b0)). Clearly, this line intersects the x -axis at (c0,0). After finding c0 using Equation (6.3), the interval [a0, b0] is divided into [a0, c0] and[c0, b0]. If f (a0)f(c0) < 0, the root is in [a0, c0]. An update is performed with b1 = c0 and a1 = a0. If f (a0) f (c0) > 0, then the update involves a1 = c0 and b1 = b0 since the root lies in [c0, b0]. The iterations continue with i = 1, and so on, until the stopping criterion is met. The same rule for the stopping criterion as in the bisection method applies, and the iterations stop once the error is smaller than ε. In the case of f (ai)f (ci) = 0, ci becomes the solution and the iteration is stopped immediately.

False position method.

Figure 6.4. False position method.

Algorithm 6.2 summarizes the computational steps in the false position method. Obviously, the steps are similar to those in Algorithm 6.1 with the difference in the update formula for ci only.

Table 6.3. Numerical results of Example 6.2

i

ai

bi

f (ai)

f (bi)

ci

f (ai) f (ci)

|cici1|

0

1.000000

2.000000

−2.000000

2.000000

1.500000

1.750000

 

1

1.500000

2.000000

−0.875000

2.000000

1.652174

0.192303

0.152174

2

1.652174

2.000000

−0.219775

2.000000

1.686611

0.010291

0.034437

3

1.686611

2.000000

−0.046826

2.000000

1.693781

0.000450

0.007169

4

1.693781

2.000000

−0.009617

2.000000

1.695246

0.000019

0.001465

Example 6.2. Find the root of f(x) = x3x2 — 2 using the false position method, given the initial values of a0 = 1 and b0 = 2, and the error |cici1| < ε, where ε = 0.005.

Solution. At i = 0, a0 = 1 and b0 = 2. We obtain f (a0) = f (1) = −2 and f (b0) = f (2) = 2. This gives

Numerical results of Example 6.2

At i = 1, a1 = 1.5 and b1 = 2. This gives f (a1) = f (1.5) = −0.875 and f(b1) = f (2) = 2. We get

Numerical results of Example 6.2

The iterations continue until i = 4, where |c4c3| = 0.001465 < ε. This produces the final solution with x * = c4 = 1.695246. The full results are shown in Table 6.3.

NEWTON-RAPHSON METHOD

The Newton-Raphson method is an open interval method that requires one initial value. This iterative method is based on a tangent line at the approximated point that provides an update at the point where this line intersects the x -axis. An initial point is any guess value that is not far from the solution. A good guess value will be x0 = a or x0 = b from the mean-value theorem condition, f (a0)f (b0) < 0. This theorem is not required in the Newton-Raphson method, but it provides a good starting point for the iterations, which contributes in faster convergence to the solution.

Figure 6.5 shows a situation for finding a root of the function f(x) with x = x0 as the initial value. A tangent line is drawn from the point (x0, f(x0)), which intersects the x -axis at (x1, 0). The value x = x1 is the new value at iteration i = 0. At i = 1, another tangent line is drawn from (x1, f(x1)), which intersects the x -axis at (x2, 0)to produce an improved value of x = x2. The same step is repeated for i = 2, 3,... until the error given by e = |xi+1xi| is smaller than a preset value ε. The final value of x is obtained at the last iteration where e < ε becomes the solution to the problem.

The formula for the Newton-Raphson method is derived by looking at the tangent line to the point (x0, f (x0)). The gradient of this tangent line is f (x0), which can also be expressed as

Equation 6.8. 

NEWTON-RAPHSON METHOD
Newton—Raphson method.

Figure 6.5. Newton—Raphson method.

The above equation is simplified further to produce

Equation 6.9. 

Newton—Raphson method.

This equation is generalized to produce the Newton—Raphson update formula, as follows:

Equation 6.4. 

Newton—Raphson method.

The computational steps in the Newton-Raphson method are summarized in Algorithm 6.3. The algorithm requires both f(x) and f'(x).

Table 6.4. Results from Example 6.3

i

xi

f(xi)

f'(xi)

|xi+1xi|

0

1.000000

−2.000000

1.000000

2.000000

1

3.000000

16.000000

21.000000

0.761905

2

2.238095

4.201706

10.551020

0.398227

3

1.839868

0.843048

6.475605

0.130188

4

1.709680

0.074396

5.349653

0.013907

5

1.695773

0.000796

5.235391

0.000152

6

1.695621

   

Example 6.3. Use the Newton-Raphson method to find the root of f(x)= x3x2 − 2, given the initial value of x0 = 1 with iterations until |xixi1| < ε, where ε = 0.005.

Solution. With f(x) = x3x2 — 2 we have f'(x) = 3x2 — 2x. At i = 0, x0 = 1 and we have x1 = x0

Results from Example 6.3

The iterations stop at i = 5, where |x6x5| = 0.000 < ε, and this produces the final solution, x* = x6 = 1.695621. Table 6.4 shows the full results from the iterations.

SECANT METHOD

One big difficulty with the Newton-Raphson method is in finding the derivative of f(x) at the iterated points. This requirement adds to the overhead on the computer, and it may also add to the computational cost in solving the problem. An alternative approach is provided in the form of the secant method, which eliminates the derivative by replacing it with a secant line.

We refer to Figure 6.6 to illustrate the method. The secant method starts with two initial guess points x0 and x1, where f (x0) ≠ f(x 1). These two points can be any points that are reliably close to the solution, and they do not have to be on the opposite sides of the x -axis. A good choice for the two initial points will be x0 = a and x1 = b, which are the end points in the interval [a, b] that abides the mean-value theorem requirement, f (a)f (b) < 0.

The first iteration with i = 0 produces an approximation at x = x2, which is the x-intercept of the chord that passes through the points (x0,f (x0)) and (x1, f (x1)). Similarly, the next approximated value x = x3 is obtained from the x-intercept of the chord that passes through the points (x1,f (x1)) and (x2,f (x2)). The same step is repeated until convergence to the solution is reached through the stopping condition e = |xi +2xi +1| < ε.

Secant method.

Figure 6.6. Secant method.

The iterative formula is derived by referring to Figure 6.6. The gradient of the line from (x0,f (x0)) to (x2, 0) is given by

Equation 6.11. 

Secant method.

which equals the gradient of the line from (x1,f (x1)) to (x2, 0), given by

Equation 6.12. 

Secant method.

The above equation is further simplified, as follows:

Equation 6.13. 

Secant method.

This produces

Equation 6.14. 

Secant method.

Replacing x0 with xi, x 1 with xi +1 and x2 with xi +2 in the above equation, we obtain the iterative formula for the secant method:

Equation 6.5. 

Secant method.

The computational steps in the secant method are summarized in Algorithm 6.4.

Example 6.4. Find the root of f(x) = x3x2 — 2 using the secant method with the initial values of x0 = 1 and x1 = 2. Perform the iterations until |xi +2xi +1 | < ε, where ε = 0.005.

Solution. At i = 0, x0 = 1 and x1 = 2. We get

Secant Method.

Table 6.5. Numerical results from Example 6.4

i

xi

f (xi)

|xi +2xi+1|

0

1.000000

−2.000000

0.500000

1

2.000000

2.000000

0.152174

2

1.500000

−0.875000

0.051042

3

1.652174

−0.219775

0.007858

4

1.703216

0.039990

0.000261

5

1.695358

  

6

1.695619

  
Reassigning the problem from f(x) = 0 to y = g(x).

Figure 6.7. Reassigning the problem from f(x) = 0 to y = g(x).

FIXED-POINT ITERATION METHOD

Through simplification, the equation f(x) = 0 maybe rewritten as x = g(x). This relationship can be obtained by making x the subject of the new equation from f(x) = 0. This representation can be viewed as reassigning the problem into finding the intersection point between the straight line y = x and the curve y = g(x) provided the solution exists. Figure 6.7 shows this connection where g(x) is totally a different curve from f(x).

The fixed-point iteration method requires an initial point x = x 0, which is a guess value relatively close to the root of f(x). Choosing x 0 = a or x 0 = b from the interval x ɛ [a, b], where f(a)f(b) < 0 is an ideal choice for this value as it is close to the solution. The reassignment from f(x) = 0 to x = g(x) gives rise to an iterative relationship given by

Equation 6.6. 

FIXED-POINT ITERATION METHOD

for i = 0, 1,2,....Convergence to the solution is guaranteed if the following condition is met:

Equation 6.7. 

FIXED-POINT ITERATION METHOD

Equation (6.7) suggests — 1 ≤ g' (x)≤1, and that the choice for the new equation x = g(x) from f(x) = 0 must strictly obey this condition. There is no guarantee for convergence to the solution if this condition is not fulfilled. Figure 6.8 illustrates a case with convergence to the solution according to x0x1x2x3 when the above condition is fulfilled.

The fixed-point iteration method is outlined in Algorithm 6.5. The algorithm is illustrated in Example 6.5.

Convergence in the fixed-point iteration method.

Figure 6.8. Convergence in the fixed-point iteration method.

Example 6.5. Find the root of f (x) = x3x2 — 2 using the fixed-point iteration method, given the initial value of x0 = 1 with iterations until |xi +1xi| < ε, where ε = 0.005.

Solution. Setting f (x) = 0 suggests several representations in the form of x = g(x), including

Fixed-Point Iteration Method.

Starting the iterations at i = 0 with the initial value of x1 = (x02 + 2)1/3 =(12 +2)1/3 = 1.442250, with g'(x0) = 0.320500. The error is |x1x 0| = |1.442250 − 1.000| = 0.442250 > ε.

At i =1, x2 = (x12 + 2)1/3 = (1.4422502 + 2)1/3 = 1.597925 and g' (x1)= 0.376562. The error is |x2x 1| = |1.597925 − 1.442250| = 0.155675 > ε.

Table 6.6. Numerical results from Example 6.5

i

xi

g (xi)

| g' (xi) |

| xi+ 1xi |

0

1.000000

1.442250

0.320500

0.442250

1

1.442250

1.597925

0.376562

0.155675

2

1.597925

1.657464

0.387772

0.059539

3

1.657464

1.680656

0.391197

0.023192

4

1.680656

1.689743

0.392416

0.009087

5

1.689743

1.693311

0.392877

0.003568

6

1.693311

   

Therefore, the process continues with the next iteration. The iterations stop at i = 5, where |x6x5| = 0.003568 < ε. The final solution is x* = x6 = 1.693311. Table 6.6 shows the complete results from this method.

VISUAL SOLUTION: CODE6

MFC provides a rich environment for developing a user-friendly interface for numeric-intensive calculations. In this chapter, we illustrate and discuss a Windows interface for finding the zeros of a function using four methods, namely, the bisection, false position, Newton-Raphson, and secant methods. The other method discussed in the last section, the fixed-point iteration method, is left as an exercise for the reader.

The Windows project for the nonlinear equations is codenamed Code6. Figure 6.9 shows a screen snapshot from the project that consists of an output from the bisection method. The input in this problem consists of the function f (x) = 3 sin 2x — 5cos(4x — 1) in0 ≤ x ≤ 1, and the results are displayed in the list view table and plotted as a graph. The solution to f(x) = 0 in this problem is displayed as x = 0.508761.

The displayed output is made up of four regions. The first region consists of the menu items that are located in the top left corner of the window. The second region consists of the input boxes that are located to the right of the menu items. The results from the calculations that are displayed in a list view table located below the menu items. The last region is located at the bottom, and it displays the solution curve.

The menu region consists of shaded rectangles for the bisection, secant, Newton-Raphson and secant methods. An item in the menu becomes activated from the left button click of the mouse. A click at one of these items prompts the creation of the input region. Edit boxes for input according to the selected method appear inside the input region. A push button called Compute is also displayed in the input region. A click at the push button causes the input to be read and processed. A complete input for the selected method will produce the desired results in the list view table and its corresponding graph in the curve region.

Output from the bisection method for f (x) = 3sin 2x — 5cos(4x — 1) in Code6.

Figure 6.9. Output from the bisection method for f (x) = 3sin 2x — 5cos(4x — 1) in Code6.

Figure 6.10 shows the development stages of Code6. A Boolean flag called fStatus monitors the progress of the execution whose initial value of 0 indicates the execution is not yet complete. Another variable called fMenu (not shown in the diagram) stores a value for the selected method: with fMenu=1 for bisection, fMenu=2 for false position, fMenu=3 for Newton—Raphson, and fMenu=4 for secant. A value for fMenu is assigned inside the function OnLButtonDown(). The stage at fStatus=1 indicates the input for the selected method has been completed, and executed. The results are displayed both in the list view table and visualized as a curve with this status.

Code6 consists of two source files, Code6.cpp and Code6.h, and the object file, MyParser.obj. A single class called CCode6 is used in this application. This class is derived from MFC's CFrameWnd, which displays a single window.

Schematic drawing showing the main computational steps in Code6.

Figure 6.10. Schematic drawing showing the main computational steps in Code6.

The main variables in this project are represented as four structures: PT, INPUT, MENU, and CURVE. PT defines the points based on the real coordinates, as described in Table 6.7. An array called pt defines the points in the real coordinates in PT.

typedef struct
{
      double x,y,yd1,a,b,c,error;
} PT;
PT *pt,max,min,left,right;

Table 6.7. The elements of PT

Variable

Type

Description

pt[i].y, pt[i].y

double

The point (xi, yi).

pt[i].a, pt[i].b

double

The left and right points in the interval aixbi.

pt[i].error

double

Error at iteration i.

pt[i].c

double

The point ci between ai and bi in the bisection and false position methods.

pt[i].yd

double

The derivative y'i = ƒ'(xi) in the Newton-Raphson method.

max, min

CPoint

The maximum and minimum points in the curve y = f (x).

left, right

CPoint

The left and right points in the curve y = f (x).

Table 6.8. Input elements in INPUT

Variable

Type

Description

input[i].item

CString

Value in string input by the user.

input[i].label

CString

Label or title for the corresponding edit box.

input[i].ed

CEdit

i th edit box for collecting the input string.

Input[i].hm

CPoint

Home coordinates of the i th edit box.

Input[i].rc

CRect

Rectangular object for the i th edit box.

Input[i].display

CRect

Rectangular area for displaying the problem.

The number of input items varies with the methods. For example, the bisection method requires four inputs, whereas the secant method requires five. A macro called maxInput is declared to store the maximum number of input, whereas nInputItems is the actual number of inputs for the selected method. Input is made using the edit boxes, which are located in the input region. A structure called INPUT defines the edit boxes and their components, and this is described in Table 6.8.

typedef struct
{
      CString label,item;
      CPoint hm;
      CEdit ed;
      CRect rc,display;
} INPUT;
INPUT input[maxInput+1];

The menu items are organized into a structure called MENU. A macro called nMenuItems stores the number of menu items whose value is four in this project. The elements of this structure are the menu titles, their rectangular objects, and their home coordinates. The elements are described in Table 6.9.

Table 6.9. Menu elements in MENU

Variable

Type

Description

menu[i].item

CString

Item i in the menu.

menu[i].hm

CPoint

Home coordinates of the i th item in the menu.

menu[i].rc

CRect

The rectangle for the i th item in the menu.

typedef struct
{
      CString item;
      CPoint hm;
      CRect rc;
} MENU;
MENU menu [nItems+1];

A structure called CURVE organizes the generated curve so that it is confined to a rectangular region called rc. The structure has its starting point and end point represented by hm and end respectively.

typedef struct
{
      CPoint hm,end;
      CRect rc;
} CURVE;
CURVE curve;

Table 6.10 describes other main variables and objects in Code6.h. The execution of the program is monitored through the value of fStatus, where fStatus=0 is the pre-evaluation stage and fStatus=1 indicates a method has been applied to the problem. Therefore, this variable is suitable to be declared as a Boolean. fMenu is an integer variable for denoting the selected method. Stop is a variable that stores the last iteration number before convergence for all methods, whereas the last value of x from this iteration is stored as Solution, which becomes the solution to the problem.

CCode6 has seven member functions that are described in Table 6.11. Bisection FPP() is a function shared by the bisection and false position methods. It is not necessary to create separate functions for these two methods as the only difference between them is the formula for ci. The Newton—Raphson and secant methods are represented by Newton() and Secant(), respectively. Other functions in Code6 are more or less similar to functions of similar names as in the previous chapters. They are described briefly in Table 6.11.

There are three events in Code6, and each one of them is handled by its corresponding function. The code is given as

Table 6.10. Other variables and objects in Code6

Variable/object

Type

Description

fStatus

bool

A flag whose values are fStatus= and fStatus=1, indicating incomplete and complete inputs, respectively.

fMenu

int

Flag for the menu whose value indicates the selected method, Menu=1 for bisection, fMenu=2 for false position, fMenu=3 for Newton-Raphson, and 0 fMenu=4 for secant.

nInputItems

int

Number of input items in the selected method.

Stop

int

The last iteration whose value is smaller or equals the stopping value, ε.

Solution

double

The last x value in the iterations before convergence.

idc

int

Id for the control resources.

btn

CButton

Push button object called Compute.

table

CListCtrl

List view table for displaying the results from iterations.

Table 6.11. Member functions in CCode6

Function

Description

CCode6()

Constructor.

∼CCode6

Destructor.

BisectionFPP()

Solves the problem using the bisection and false position methods.

Newton()

Solves the problem using the Newton—Raphson method.

Secant()

Solves the problem using the secant method.

DrawCurve()

Draws the curve y = f (x) in the given interval.

ShowTable()

Creates a list view table to display the results.

OnLButtonDown()

Responds to ON WM LBUTTONDOWN, which the assigns the method in the menu.

OnButton()

Responds to ON_BN_CLICKED, which reads the input from the user and calls the respective method to produce its solution.

OnPaint()

Displays and updates the output in the main window.

BEGIN_MESSAGE_MAP(CCode6,CFrameWnd)
      ON_WM_PAINT()
      ON_WM_LBUTTONDOWN()
      ON_BN_CLICKED(IDC_BUTTON,OnButton)
END_MESSAGE_MAP()

The process starts at the constructor. Basically, the constructor allocates memory for the class. It is also in the constructor that several variables are assigned with their initial values. The initial values include the location of the objects, the size of each object, and its title. It is also important to set the initial value for fStatus=0 to indicate no input has been read yet at this level of execution. At this initial stage, the menu has not been displayed, and its flag is set to fMenu=0. The code is given as

CCode6::CCode6()
{
      Create(NULL,"Code6: Nonlinear Equations",
            WS OVERLAPPEDWINDOW,CRect(0,0,800,635),NULL);
      Arial80.CreatePointFont(80,"Arial");
      pt=new PT [m+1];
      fMenu=0; fStatus=0; idc=301
      menu[1].item="NLE: Bisection";
      menu[2].item="NLE: False-Point Position";
      menu[3].item="NLE: Newton-Raphson";
      menu[4].item="NLE: Secant";
      for (int i=1;i<=nMenuItems;i++)
      {
           menu[i].hm= CPoint(20,30+(i-1)*30);
           menu[i].rc=CRect(menu[i].hm.x,menu[i].hm.y,
                 menu[i].hm.x+150,menu[i].hm.y+20);
      }
      curve.hm=CPoint(320,320); curve.end=CPoint(760,590);
      curve.rc=CRect(curve.hm.x-10,curve.hm.y-10,
            curve.end.x+10,curve.end.y+10);
      input[0].hm=CPoint(200,10);
      input[0].rc=CRect(input[0].hm.x,input[0].hm.y,
            input[0].hm.x+560,input[0].hm.y+260);
      input[0].display=CRect(30,180,170,250);
      for (i=1;i<=maxInput;i++)
            input[i].hm=CPoint(input[0].hm.x+10,input[0].
                     hm.y+30+(i-1)*25);
}

OnPaint() displays and updates the output regularly. The initial display in the main window consists of the menu items that are located in shaded rectangular boxes in the menu region. An update is made immediately whenever InvalidateRect() is invoked. The update is made possible through the test

if (fMenu!=0)

This conditional test separates the initial screen display from the time when a method has been selected. Another test written as

if (fStatus)

indicates the method has been successfully applied and produces its solution. The message updates the window by drawing the corresponding curve through DrawCurve() and displaying the solution. If the method fails, for example in the case of the absence of a root in the given interval, then the message No solution is displayed. The complete code for OnPaint() is shown below:

void CCode6::OnPaint()
{
      CPaintDC dc(this);
      CString str;
      dc.SelectObject(Arial80);
      dc.SetBkColor(RGB(150,150,150));
      dc.SetTextColor(RGB(255,255,255));
      for (int i=1;i<=nMenuItems;i++)
      {
            dc.FillSolidRect(&menu[i].rc,RGB(150,150,150));
            dc.TextOut(menu[i].hm.x+5,menu[i].hm.y+5,menu
                   [i].item);
    }
    dc.SetBkColor(RGB(255,255,255));
    dc.SetTextColor(RGB(100,100,100));
    dc.Rectangle(input[0].rc);
    dc.TextOut(input[0].hm.x+150,
          input[0].hm.y+5,menu[fMenu].item);
    if (fMenu!=0)
    {
          for (i=1;i<=nInputItems;i++)
                dc.TextOut(input[i].hm.x+10,
                input[i].hm.y,input[i].label);
          if (fStatus)
          {
                DrawCurve();
                str.Format("Solution: x=%lf",Solution);
                dc.TextOut(input[0].display.left,input
                       [0].display.top,str);
                fStatus=0;
          }
          else
                dc.TextOut(input[0].display.left,input
                       [0].display.top, "No solution");
    }
}

OnLButtonDown() repondsto ON_WM_LBUTTONDOWN, which corresponds to a click at one of the items in the menu. With the menu displayed, the user has the choice of four methods from which to choose. OnLButtonDown() handles this event and returns true through the test

if (menu[k].rc.PtInRect(pt))

The above conditional test makes sure that only a left-click at the menu items is valid. The click at one of the menu items causes the assignment of a number to fMenu, with fMenu=1 for bisection, fMenu=2 for false position, fMenu=3 for Newton— Raphson, and fMenu=4 for secant. With this selection, edit boxes and the Compute push button appear in the input region. The complete code for OnLButtonDown() is shown below:

void CCode6::OnLButtonDown(UINT nFlags,CPoint pt)
{
      int i,k;
      for (k=1;k<=nMenuItems;k++) // menu items
            if (menu[k].rc.PtInRect(pt))
            {
                  fMenu=k;
                  InvalidateRect(input[0].rc);
                  switch(k)
                  {
                        case 1:
                        case 2:
                              nInputItems=4;
                              input[1].label="y=f(x)";
                              input[2].label="a[0]";
                              input[3].label="b[0]";
                              input[4].label="epsilon";
                              break;
                        case 3:
                              nInputItems=5;
                              input[1].label="y=f(x)";
                              input[2].label="y=f'(x)";
                              input[3].label="x[0]";
                              input[4].label="x[m]";
                              input[5].label="epsilon";
                              break;
                        case 4:
                              nInputItems=5;
                              input[1].label="y=f(x)";
                              input[2].label="x[0]";
                              input[3].label="x[1]";
                              input[4].label="x[m]";
input[5].label="epsilon";
                              break;
                  }
                  for (int i=1;i<=maxInput-1;i++)
                        input[i].ed.DestroyWindow();
                  btn.DestroyWindow();
                  btn.Create("Compute",WS CHILD|WS VISIBLE|
                        BS DEFPUSHBUTTON,
                        CRect(CPoint(input[0].hm.x+10,
                        input[0].hm.y+5),
                        CSize(100,20)),this,IDC BUTTON);
                  for (i=1;i<=nInputItems;i++)
                        input[i].ed.Create(WS CHILD|
                              WS VISIBLE| WS BORDER,
                              CRect(input[i].hm.x+100,
                              input[i].hm.y,input[i].hm.x+520,
                              input[i].
                              hm.y+20),this,idc++);
      }
}

The user now completes the input by filling in the values in the edit boxes. Once this is done, a click at Compute confirms the entries, and the process proceeds into the next stage. This event is detected as ON_gBN_CLICKED, and its message handler is OnButton(). This function reads the input values, and then calls the corresponding function according to its fMenu value.

void CCode6::OnButton()
{
      CString str;
      if (fMenu!=0)
      {
            for (int i=1;i<=nInputItems;i++)
                 input[i].ed.GetWindowText(input[i].item);
            if (fMenu==1 | | fMenu==2)
                  BisectionFPP();
            if (fMenu==3)
                  Newton();
            if (fMenu==4)
                  Secant();
            if (fStatus)
            {
                  ShowTable();
                  InvalidateRect(curve.rc);
InvalidateRect(input[0].display);
            }
            else
                  InvalidateRect(input[0].display);
      }
}

In the above code segment, a conditional test is performed to determine whether the problem has been solved successfully using the selected method. This is done through

if (fStatus)

A successful method is one that converges to its solution. This is indicated through the assignment of fStatus=1. A message is also displayed in the main window if the method fails, which is indicated through fStatus=0.

The value of the status flag fStatus is updated inside one of the functions from BisectionFPP(), Newton(), and Secant(). An update of fStatus=1 indicates the input data have been successfully executed in the selected method. A value of 0 means the method fails mostly from wrong end points in locating the root. With fStatus=1, the process now proceeds to the next stage, which is a display of the results in the table through ShowTable(). The graph of y = f (x) is also drawn through an update in OnPaint() using InvalidateRect(). The final solution in the form of an approximated value of the root to y = f (x) is displayed through InvalidateRect(input[0].display).

The bisection and the false position methods are handled by BisectionFPP(). In terms of concepts, the two methods are similar as they are based on a fixed starting interval given by a 0xb 0. The only difference between them is the way an update is made on ci. The code segment for BisectionFPP() is given by

void CCode6::BisectionFPP()
{
      int i,psi[2];
      double fa,fb,fc,psv[2],epsilon;
      pt[0].a=atof(input[2].item);
      pt[0].b=atof(input[3].item);
      epsilon=atof(input[4].item);
      psi[1]=23; psv[1]=pt[0].a;
      fa=parse(input[1].item,1,psv,psi);
      psv[1]=pt[0].b;
      fb=parse(input[1].item,1,psv,psi);
      if (fa*fb<0)
      {
            for (i=0;i<=maxIter;i++)
        {
if (fMenu==1)
                         pt[i].c=(pt[i].a+pt[i].b)/2;
                   if (fMenu==2)
                         pt[i].c=(pt[i].a*fb-pt[i].b*fa)/(fb-fa);
                   psv[1]=pt[i].a;
                   fa=parse(input[1].item,1,psv,psi);
                   psv[1]=pt[i].b;
                   fb=parse(input[1].item,1,psv,psi);
                   psv[1]=pt[i].c; pt[i].x=pt[i].c;
                   fc=parse(input[1].item,1,psv,psi);pt[i].y=fc;
                   if (fa*fc>0)
                   {
                         pt[i+1].a=pt[i].c; pt[i+1].b=pt[i].b;
                   }
                   else
                   {
                         pt[i+1].b=pt[i].c; pt[i+1].a=pt[i].a;
                   }
                   if (i>0)
                   {
                         pt[i].error=fabs(pt[i].c-pt[i-1].c);
                         if (pt[i].error<epsilon)
                         {
                               Stop=i;
                               Solution=pt[Stop].c;
                               fStatus=1;
                               break;
                         }
                   }
           }
    }
}

The bisection and false position methods will only produce the desired results if the starting interval complies with the mean-value theorem. In the above code segment, a conditional test is performed for this possibility, as follows:

if (fa*fb<0)

In the above test, fa and fb are f (a0) and f (b0), respectively. This test performs a check on the validity of the starting points in the interval through the mean-value theorem.

The iterations in the bisection and false position methods are governed by a comparison on the error, |cici −1| < ε or pt[i].error<epsilon. A check is made at every iteration to determine whether the stopping criteria have been achieved, through

pt[i].error=fabs(pt[i].c-pt[i-1].c);
if (pt[i].error<epsilon)
{
       Stop=i;
       Solution=pt[Stop].c;
       fStatus=1;
       break;
}

Several assignments are made if the stopping mark for the iterations has been achieved. First, the last iteration number is stored as Stop. The last value of ci is then the final solution, and this value is assigned to Solution. Finally, the status flag is assigned with a value of 1 to indicate the method is successful.

The Newton—Raphson method is handled by Newton(). This method is easier to implement than the bisection and false position methods as the method does not involve an update at the end points of the intervals. The code is shown as follows:

void CCode6::Newton()
{
      int i,psi[2];
      double epsilon,psv[2];
      pt[0].x=atof(input[3].item);
      pt[m].x=atof(input[4].item);
      epsilon=atof(input[5].item);
      psi[1]=23;
      for (i=0;i<=maxIter;i++)
      {
            psv[1]=pt[i].x;
            pt[i].y=parse(input[1].item,1,psv,psi);
            pt[i].yd1=parse(input[2].item,1,psv,psi);
            if (i<m)
            {
                     pt[i+1].x=pt[i].x-pt[i].y/pt[i].yd1;
                     pt[i].error=fabs(pt[i+1].x-pt[i].x);
            }
            if (pt[i].error<epsilon)
            {
                 Stop=i;
                 Solution=pt[Stop].x;
                 fStatus=1;
                 break;
            }
     }
}

A difficulty with the Newton-Raphson method is the requirement of the first derivative in its update formula. Since our application does not support symbolic computing for determining the derivative, it is necessary for the user to enter the input string. In the above code segment, f'(x) is denoted as pt[i].yd1, and its string value is read as input[2].item in theedit box. This string will be processed through parse() to produce the numerical value of f'(x) at the given point.

The error in the Newton-Raphson method is |xi+1xi|, and this is written as pt[i].error=fabs(pt[i+1].x-pt[i].x). The error is again compared with epsilon at each iteration to determine whether convergence has been achieved.

The secant method is handled by Secant(). The method works on the same concept as in the Newton-Raphson method with the derivative replaced by an approximation using a secant line. The code segment for Secant() is given by

void CCode6::Secant()
{
      int i,psi[2];
      double epsilon,psv[2];
      pt[0].x=atof(input[2].item);
      pt[1].x=atof(input[3].item);
      pt[m].x=atof(input[4].item);
      epsilon=atof(input[5].item);
      psi[1]=23; psv[1]=pt[0].x;
      pt[0].y=parse(input[1].item,1,psv,psi);
      for (i=0;i<=maxIter;i++)
      {
             if (i<m-2)
            {
                  psv[1]=pt[i+1].x;
                  pt[i+1].y=parse(input[1].item,1,psv,psi);
                  pt[i+2].x=(pt[i].x*pt[i+1].y
                        -pt[i+1].x*pt[i].y)/(pt[i+1].y-pt[i].y);
                  pt[i].error=fabs(pt[i+2].x-pt[i+1].x);
             }
             if (pt[i].error<epsilon)
             {
                   Stop=i;
                   Solution=pt[Stop].x;
                   fStatus=1;
                   break;
             }
      }
}

The two-ahead update in the value of x for the secant method is shown in the above code segment through

if (i<m-2)
{
     psv[1]=pt[i+1].x;
     pt[i+1].y=parse(input[1].item,1,psv,psi);
     pt[i+2].x=(pt[i].x*pt[i+1].y-pt[i+1].
         x*pt[i].y)/(pt[i+1].y-pt[i].y);
     pt[i].error=fabs(pt[i+2].x-pt[i+1].x);
}

The error in the secant formula is pt[i].error=fabs(pt[i+2].x-pt[i+1].x) or |xi +2xi +1|. As in the other three methods, the error is compared with epsilon at each iteration to determine whether a stopping mark has been reached.

The results from the iterations in the selected method are tabulated in the list view table through ShowTable(). The items displayed are the iteration number i, xi, f (xi) and the corresponding error values. The code segment for ShowTable() is given by

void CCode6::ShowTable()
{
      CString str;
      CPoint hTable=CPoint(20,310);
      CRect rcTable=CRect(hTable.x,hTable.y,hTable.x+280,
      hTable.y+290);
      table.DestroyWindow();
      table.Create(WS VISIBLE | WS CHILD | WS DLGFRAME |
              LVS REPORT| LVS NOSORTHEADER,rcTable,this,idc++);
      table.InsertColumn(0,"i",LVCFMT CENTER,25);
      table.InsertColumn(1,"x",LVCFMT CENTER,70);
      table.InsertColumn(2,"f(x)",LVCFMT CENTER,70);
      table.InsertColumn(3,"error",LVCFMT CENTER,70);
      for (int i=0;i<=Stop;i++)
      {
            str.Format("%d",i); table.InsertItem(i,str,0);
            str.Format("%lf",pt[i].x);
            table.SetItemText(i,1,str);
            str.Format("%lf",pt[i].y);
            table.SetItemText(i,2,str);
            if (fMenu>2 | | ((fMenu<=2) &&i>0))
            {
                  str.Format("%lf",pt[i].error);
                  table.SetItemText(i,3,str);
            }
      }
}

DrawCurve() is basically the same function as discussed earlier in Chapter 4. There are some modifications to cater the needs in the methods discussed. The graph is not drawn if the selected method fails. The full code segment for DrawCurve() is given as follows:

void CCode6::DrawCurve()
{
      CClientDC dc(this);
      int i,psi[2];
      double h,m1,m2,c1,c2,psv[2];
      CString str;
      CPoint px;
      psi[1]=23;
      if (fMenu==1 | | fMenu==2)
      {
            left.x=pt[0].a;
            right.x=pt[0].b;
      }
      if (fMenu==3 | | fMenu==4)
      {
            left.x=Solution-2;
            right.x=Solution+2;
      }
      h=(right.x-left.x)/(double)m;
      pt[0].x=left.x; psv[1]=pt[0].x;
      pt[0].y=parse(input[1].item,1,psv,psi);
      max.y=pt[0].y; min.y=pt[0].y;
      for (i=1;i<=m;i++)
      {
           pt[i].x=pt[i-1].x+h;
           psv[1]=pt[i].x;
           pt[i].y=parse(input[1].item,1,psv,psi);
           if (max.y<pt[i].y)
                 max.y=pt[i].y;
           if (min.y>pt[i].y)
                 min.y=pt[i].y;
      }

      // Cartesian-Windows conversion coordinates
      m1=(double)(curve.end.x-curve.hm.x)/(right.x-left.x);
      c1=(double)curve.hm.x-left.x*m1;
      m2=(double)(curve.hm.y-curve.end.y)/(max.y-min.y);
      c2=(double)curve.end.y-min.y*m2;
// Draw & label the x,y axis
      CPen pGray(PS SOLID,1,RGB(100,100,100));
      dc.SelectObject(pGray);
      dc.SelectObject(Arial80);
      px=CPoint(m1*0+c1,m2*min.y+c2); dc.MoveTo(px);
      px=CPoint(m1*0+c1,m2*max.y+c2); dc.LineTo(px);

      px=CPoint(m1*left.x+c1,m2*0+c2); dc.MoveTo(px);
      str.Format("%.0lf",left.x); dc.TextOut(px.x,px.y,str);
      px=CPoint(m1*right.x+c1,m2*0+c2); dc.LineTo(px);
      str.Format("%.0lf",right.x); dc.TextOut(px.x-10,px.y,str);

      // draw the curve
      CPen pDark(PS SOLID,2,RGB(50,50,50));
      dc.SelectObject(pDark);
      for (i=0;i<=m;i++)
      {
            px=CPoint((int)(m1*pt[i].x+c1),
                      (int)(m2*pt[i].y+c2));
            if (i==0)
                  dc.MoveTo(px);
            else
                  dc.LineTo(px);
            if (pt[i].y==max.y)
            {
                  str.Format("%.6lf",max.y);
                  dc.TextOut(px.x,px.y-10,str);
            }
            if (pt[i].y==min.y)
            {
                  str.Format("%.6lf",min.y);
                  dc.TextOut(px.x,px.y,str);
            }
      }
}

The interval of x for the curve has been set differently the methods. In the case of the bisection and false position methods, DrawCurve() draws a graph based on the input values in the left and right intervals, or a 0xb 0. This is given by

if (fMenu==1 | | fMenu==2)
{
      left.x=pt[0].a;
      right.x=pt[0].b;
}

SUMMARY

We discussed five methods for finding the zeros of a function f(x), namely, the bisection, false position, Newton-Raphson, secant, and fixed-point iteration methods. Four methods are illustrated in their visual interfaces using Visual C++. Each method is represented as an item in the menu. The solution for each method is shown in a list view table and is depicted in its solution curve. This interface provides the fundamental requirement for visualization on the nonlinear equation problems.

The nonlinear problem arises in many science and engineering applications. In many cases, the problem appears as a small component from the overall problem. Solving the problem as a component definitely contributes to solving the overall problem. The Windows-friendly interface for handling the numerical computations using C++ helps much in contributing to the solution.

NUMERICAL EXERCISES

  1. Referring to the mean-value theorem, determine whether at least one root exists in the given intervals of the following problems:

    1. f(x) = 1 − 3x, x ɛ [− 1, 2].

    2. f(x) = 1 − 3x + 5x2, x ɛ [−1, 2].

    3. f(x) = 1 − 3x + 5x2 − 6x4, x ɛ [−1,2].

    4. f(x) = 3sinx − 5cosx, x ɛ [−1, 2].

    5. f(x) = 3sin(2x − 1) − 5cos(1 − 2sinx)), x ɛ [−1, 2].

    Check the results through their corresponding graphs by running Code6.

  2. Solve each problem in Question 1 using the following methods:

    1. Bisection method.

    2. False position method.

    3. Newton-Raphson method.

    4. Secant method.

    5. Fixed-point iteration method.

    Check the results by running Code6.

  3. The graph from a given function provides a rough estimate on the location of one or more roots in the function. This is obvious in the case of the occurrence of multiple roots in the function. From the graph, a single root in a refined interval can be obtained. Run Code6 to locate the single roots of the following functions by refining their intervals using this approach:

    1. f(x) = 1 − x2 + 2x3 − 5x4 + 2x7, x ɛ [−2, 2].

    2. f(x) = 3x4 − 5x2 + 1, x ɛ [− 1, 3].

    3. f(x) = −3cos2x + 3sinx, x ɛ [−6, 2].

    4. f(x) = 3sin(2x − 1) − 5cos(1 − 2sinx)), x ɛ [−1, 2].

PROGRAMMING CHALLENGES

  1. Improve on Code6 by adding the fixed-point iteration method as an item in the menu. Add a mechanism to check for the convergence of the method by checking for |g' (x)| < 1 at every iteration, where x = g(x) is derived from f(x) = 0.

  2. Code6 generates a graph and its solution only when the product of the left and right end points is negative. Modify the project to display the graph even if the product is positive.

  3. An intelligent root finder is one that can determine a small subinterval from a given function where its root is located automatically without the necessity of guessing two end points beforehand. This task can be added to Code6 by adding a routine to check for the occurrence of a root at every small subinterval from a given interval. Modify Code6 to add this option.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset