• Aucun résultat trouvé

Least-Squares Approximation

Dans le document in C++ and MPI (Page 139-150)

float + floatImplicit Casting

3.1.7 Least-Squares Approximation

3.5 4

Figure 3.9: Plot of the B-spline. Its support extends over four intervals.

3.1.7 Least-Squares Approximation

What we have dealt with so far are interpolations for which the number of unknowns matches the number of constraints in the function values or slopes. The cubic splines are perhaps a slight exception, as extra information is needed to determine them, but that too could be cast in a form of a linear system

C a= y,

where ais the vector of unknowns and y is the vector of prescribed values.

In practical scientific computing, however, the opposite situation may occur where we may have much more informationthan we actually require. For example, consider the case of data analysis in an experiment with many measurements and with the dependent variable following a quadratic trend. It is clear that, unless we are really lucky, not all measurements will lie on a parabola! It is also clear that not all measurements have the same confidence associated with them, and this should be reflected in the polynomial interpolant. The question then is what type of parabola fits best the data and has built-in the measurements-accuracy reported.

A similar situation arises in the problem of smoothing the data, a familiar task in engi-neering, and Gauss was the first to provide a general treatment of this subject. He introduced the bracket notation which we will use here, i.e., for a set ofm grid points we define

[y]≡y1+. . .+ym and the moments

[wxk] =w1xk1 +w2xk2 +. . .+wmxkm.

To obtain the least-squares polynomial, we assume that we have m points and the pairs (xi, yi), i= 1, . . . , m, and we try to represent them with a polynomial p(x) of degree n

p(x) =a0+a1x+a2x2+. . .+anxn,

where m n+ 1. The strict inequality corresponds to an over-determined system that we discuss in this section. In matrix-vector form, if we follow the straightforward path, as in the Vandermonde approach (section 3.1.1), we have that

C(m×(n+ 1))×a(n+ 1) =y(m),

so the matrix C is rectangular, and for m > (n+ 1) this system does not always have a solution.

However, there is a minimum principle associated with this problem. That is, instead of solving the system C a = y we attempt to find the vector a such that we minimize the residual

r(a) =C ay2 . This results in solving a system of the form

CTC a=CTy, (3.10)

where CTC is a symmetric positive definite matrix ifChas linearly independent columns.

This may not be obvious at first but following Gauss, we can derive thenormal equations that result in the matrix-vector form of equation (3.10). To this end, we compute the residual

ri =a0+a1xi+. . .+anxni −yi

where wi is a weight that reflects confidence in the accuracy of measurement at point xi. The next step is to minimize the residual R by taking its derivatives with respect to all ai and setting them to zero, i.e.,

∂R

∂ai = 0 for i= 1, . . . , m.

Using the Gauss notation, this yields

a0[wx0] + a1[wx1] +. . .+an[wxn] = [wy]

which is an (n+ 1)×(n+ 1) matrix, the so-called Hankel matrix, with its cross-diagonals constant. Also, if we form

W=

the matrix of weights, then we can write the Hankel matrix as H=VTWV

is the rectangular Vandermonde matrix; it is non-singular if at least (n+ 1) points (out of the total m points) are distinct. The normal equations can be recast in matrix-vector form as

(VTWV)a=VTWy where

yT = (y1, y2, . . . , ym) are the measurements.

The normal equations are, in some sense, a generalization of the Vandermonde approach where the set

We can change the basis, as we did in the case of deterministic interpolation, in order to arrive at a better algorithm to compute the coefficients (ao, a1, . . . an) via recursion. To this

If this basis isorthonormal, i.e.,

then the matrix G is a diagonal matrix and the computation of the unknown vector a becomes a trivial matter. We have seen that the Chebyshev polynomials are orthonormal but their construction requires special Gauss-Lobbato points (see section 3.1.5). Here the key is to discover a similar three-term recurrence formula for a given arbitrary distribution of points {xi}. We expect that such a three-term recurrence formula is possible given that the Gram-Schmidt orthogonality procedure of this form for vectors leads to that form, see section 2.2.9; similar results should be expected for polynomials.

Let us call the basis φk(x) =qk(x) where qk(x) is the orthonormal polynomial which, by assumption, satisfies the recursion

qj+1(x) =xqj(x)−αj+1qj(x)−βjqj1(x), j = 1, . . . ,(n1). (3.11) We need to find recurrence relations for the constantsαi+1andβj as well as initial conditions.

From the orthonormality constraint we have that q0(x) = 1

and in general, we define

γk m

i=1

wiq2k(xi).

Similarly, we can obtain the coefficients α2 and β2 by insisting that q2(x) be orthogonal to q1(x) andq0(x), which are the two previous polynomials. By induction we obtain the general result from the following two orthogonality constraints:

m i=1

wiqj+1(xi)qj(xi) = 0; qj+1 qj (3.12)

m i=1

wiqj+1(xi)qj1(x1) = 0, qj+1 ⊥qj1 (3.13) These conditions are sufficient to prove that qj+1 ⊥qk,k = 0, . . . , j2.

By substituting the recurrence formula (equation (3.11)) for qj+1 in equations (3.12) and (3.13), we obtain

We can now write the following recursive algorithm for computing the orthogonal polynomials qk(x):

Having constructed all the orthogonal polynomials qk(x), the unknown coefficients are com-puted from

is the least-squares polynomial.

Remark: There is a similarity between the procedure we just described and the QR de-composition presented in section 2. Recall that the problem of finding a least-square polynomial is equivalent to solving the system

CTC a =CT y.

We can apply QR decomposition to matrix C, to obtain C=QR and the above equation becomes

RTR a=RTQTy or

R a=QTy

with R being upper triangular. The vector of unknown coefficients a is then obtained by back substitution. In practice, another version of QR factorization is preferable, the so-called Householder triangulaziation, which we will study in chapter 9.

Software Suite

Putting it into Practice

From the discussion above, we see that given a set of data, we need to calculate and store three quantities: αi, βi, and the set of least-squares coefficients. We will accomplish this with the function presented below. This function takes as input the number of points over which the least-squares approximation is to be calculated (npts), an array of positions x, data values (or function values) at the previously mentioned spatial points stored in the array f uncvals, the degree of the least-squares approximation ndeg, and then as output arrays this routine will fill in the arrays alpha, beta and lscoef f s. Note that this function assumes that all the arrays have been allocated:

void LS_ComputeCoeffs(int npts, double *xpts, double *funcvals, int ndeg, double *alpha, double *beta, double *lscoeffs){

int i,j;

double xi,tmpd;

double * gamma = new double[ndeg+1];

//////////////////////////

// Compute average first

xi = 0.0;

for(i=0;i<npts;i++){

xi += xpts[i];

}

xi /= (double) npts;

/////////////////////////

gamma[0] = npts;

alpha[0] = beta[0] = 0.0;

alpha[1] = xi;

for(j=1;j<=ndeg-1;j++){

gamma[j] = 0.0;

alpha[j+1] = 0.0;

for(i=0;i<npts;i++){

tmpd = LS_OrthoPoly(j,xpts[i],alpha,beta);

gamma[j] += tmpd*tmpd;

alpha[j+1] += xpts[i]*tmpd*tmpd;

}

alpha[j+1] /= gamma[j];

beta[j] = gamma[j]/gamma[j-1];

}

gamma[ndeg] = 0.0;

for(i=0;i<npts;i++){

tmpd = LS_OrthoPoly(ndeg,xpts[i],alpha,beta);

gamma[ndeg] += tmpd*tmpd;

}

beta[ndeg] = gamma[ndeg]/gamma[ndeg-1];

for(j=0;j<=ndeg;j++){

lscoeffs[j] = 0.0;

for(i=0;i<npts;i++)

lscoeffs[j] = lscoeffs[j] + funcvals[i]*

LS_OrthoPoly(j,xpts[i],alpha,beta);

lscoeffs[j] /= gamma[j];

}

delete[] gamma;

return;

}

There are two issues that we would like to point your attention to as you examine this function:

1. Remark 1: If you examine the mathematical formulation carefully, you notice that the above function relies on the orthogonal polynomial function being defined. The definition for the orthogonal polynomial requires the α and β to be defined. At first glance, there appears to be a circular dependency! However, it is not. Observe that whenever the orthogonal polynomial needs to know α and β, they have already been properly calculated. The point: this is a highly inductive process. The ordering of calculation is very important in this routine. You should take the time to chart out the dependencies, and observe how timing is everything!

2. Remark 2: Dynamic memory allocation within functions is quite natural, and most programmers have no problem allocating arrays within functions. However, many programmers become negligent, and do not deallocate the temporary memory that they needed. In the function above, the arraygammais allocated for temporary usage within this function. Note that this temporary array is deallocated (using thedelete[]

command) just prior to the function returning. Recall that the local variables within a function are local to the function, and go away when the function concludes. Hence, if memory is allocated togammaand is not returned to the system prior to the function returning, that memory is “lost” for the remainder of the runtime of your program!

This is what is referred to as a memory leak. If you view memory as a conserved quantity (that is, that for every allocation there is a deallocation), then if you forget to deallocate a piece of memory prior to it being inaccessible by the user (in this case due to the pointer variable going away when the function returns), then memory has “leaked,” and hence the total amount of memory available for dynamic memory allocation is reduced.

WARNING Programmer Beware!

Beware of memory leaks!

For every allocate

(new) there should be a deallocate (delete[]) Using the Switch

Software Suite

Once again, we use the concept of recursion for quickly imple-menting the mathematical definition of the orthogonal polyno-mial. Again, we have two base cases, when the index of the polynomial is 0 or 1; for all other positive values of the index, the value of the polynomial is calculated using the recursion relation.

double LS_OrthoPoly(int j, double x, double *alpha, double *beta){

int i;

double value;

switch(j){

case 0:

value = 1.0;

break;

case 1:

value = x - alpha[j];

break;

default:

value = (xalpha[j])*LS_OrthoPoly(j1,x,alpha,beta) -beta[j-1]*LS_OrthoPoly(j-2,x,alpha,beta);

break;

}

return value;

}

Once the coefficients have been calculated, then the least-squares approximating polyno-mial can be evaluated at any point. Below we present the implementation of this function.

double LSApproximatingPoly(int ndeg, double x, double *alpha, double *beta, double *lscoeffs){

double value = 0.0;

for(int i=0;i<=ndeg;i++)

value += lscoeffs[i]*LS_OrthoPoly(i, x, alpha, beta);

return value;

}

C++ Compound Assignment

Look carefully at the function above. You will notice something new: the “+=” operator.

This is a convenient C++ shorthand used for accumulation. The C++ statement a += b;

is equivalent to the statement a = a + b;

Shorthand Description i++ Pre-increment, i = i + 1 ++i Post-increment, i = i + 1

i– Pre-decrement, i = i - 1 –i Post-decrement, i = i - 1

i += j i = i + j

i -= j i = i - j

i *= j i = i * j

i /= j i = i / j

Table 3.1: C++ compound assignment operations.

which is to be interpreted as taking the value of b and accumulating it to a. Table 3.1 gives a collection of these “shorthand” programming notations used in C++.

Pre- and post-incrementing/decrementing may be somewhat confusing at first, but con-sider the following code

j = i++;

k = ++p;

in which we use the post-incrementor in the first line, and the pre-incrementor in the second line. If we expand this shorthand notation into its traditional C++ code, we obtain the following:

j = i;

i = i + 1;

p = p + 1;

k = p;

Notice that in the first example, the post-incrementor is used, so the assignment is accomplished first, and then the increment. The exact opposite happens when the pre-incrementor is used. When used as an individual statement (such as we have used it in for statements), the two give identical results.

WARNING Programmer Beware!

C++ shorthands can be convenient but deadly! A slip of the finger, and += can be =+, which is a valid

C++ statement, setting one value as the positive value of another value! Not what was intended!!

Software Suite

Below, we present a program which uses the functions de-scribed above. Notice the general structure of this program:

1. Query the user to obtain information.

2. Allocate necessary memory (dynamically).

3. Produce a grid, and evaluate the function to be approx-imated.

4. Compute least squares coefficients by calling LS ComputeCoeffs.

5. Evaluate approximating polynomial on a fine grid for plotting.

6. Deallocate dynamic memory used within the program.

#include <iostream.h>

#include "SCchapter3.h"

double func(double x);

int main(int argc, char * argv[]){

int i;

int degree, polypnts;

int npts = 1000; //number of points used for plotting double xpt, soln, approx;

cout << "Enter the degree of the least squares polynomial: ";

cin >> degree;

cout << "Enter the number of points to use for evaluation: ";

cin >> polypnts;

double * poly_xpts = new double[polypnts];

double * func_vals = new double[polypnts];

double * alpha = new double[degree+1];

double * beta = new double[degree+1];

double * lscoeffs = new double[degree+1];

CreateGrid_EvenlySpaced(polypnts, poly_xpts, -1.0, 1.0);

for(i=0;i<polypnts;i++){

func_vals[i] = func(poly_xpts[i]);

}

LS_ComputeCoeffs(polypnts, poly_xpts, func_vals, degree, alpha, beta, lscoeffs);

for(i=0;i<npts;i++){

xpt = -1.0 + 2.0*i/(npts-1);

soln = func(xpt);

approx = LSApproximatingPoly(degree, xpt, alpha, beta, lscoeffs);

cout << xpt << " " << soln << " " << approx << endl;

}

delete[] alpha;

delete[] beta;

delete[] lscoeffs;

delete[] poly_xpts;

delete[] func_vals;

}

double func(double x){

double y;

y = 1.0 + 25.0*x*x;

y = 1.0/y;

return y;

}

Key Concept

As a programmer, you should have a gameplan! Always take a few moments to formulate the general structure of your program;

this will save you much time in the end!

Dans le document in C++ and MPI (Page 139-150)