• Aucun résultat trouvé

Polynomial Representation

Dans le document in C++ and MPI (Page 103-108)

Example of Usage

3.1 Polynomial Representation

In this section we will study different ways of interpolating data on equidistant and more general grids using polynomials. We will discuss both accuracy and efficiency and will introduce C++ arrays and other concepts to effectively implement the algorithms.

3.1.1 Vandermonde and Newton Interpolation

Assuming that we have the data available on the discrete set of points{x0, x1, . . . , xN}with corresponding values {f(x0), f(x1), . . . f(xN)}, then we can construct a function f(x) that passes through the pairs (xi, f(xi)) by the approximation

f(x)≈pN(x) =

N k=0

akφk(x),

where pN(x) is referred to as the interpolating polynomial, φk(x) are a priori known poly-nomials, andak are theunknown coefficients. We call φk(x) the basis, and its choice is very important in obtaining anefficient approximation. For example, assuming thatφk(x) =xk, k = 0, . . . , N, then we have the following representation at the known pair (xi, f(xi))

f(xi) =a0+a1xi+a2x2i +. . .+aNxNi , i= 0, . . . N .

All together we have (N + 1) such equations for the (N + 1) unknowns ai, i = 0, . . . , N. This system of equations can be recast in matrix form with the vector of unknowns aT = (a0, a1, a2, . . . , aN) as follows

where the matrix V is known as the Vandermonde matrix. This matrix is non-singular because we assume that all {x0, x1, . . . xN} are distinct points, and therefore there exists a unique polynomial of order N that represents this data set. We could obtain the vector of coefficients a from

a=V1f,

by inverting the Vandermonde matrix V and subsequently performing matrix-vector mul-tiplications with f(xi). This, however, is an expensive operation with a cost of O(N3) to invert the matrix V (see chapter 9), and it is rarely used in practice.

One approach in reducing the computational complexity is to simply change the basis to φk(x) = Πki=01(x−xi),

so f(x) is now approximated by

f(x)≈a0+a1(x−x0) +a2(x−x0)(x−x1) +. . .+aN(x−x0)(x−x1). . .(x−xN1). (3.1) Notice that we still use a polynomial basis, but we have simply shifted it with respect to the coordinates of the data points. This simple shift turns out to have a dramatic effect since now the newunknown coefficients can be computed by inverting the following system

which is a lower triangular matrix and requires only O(N2) operations in order to obtain the vector of unknown coefficients. This is done by simple forward substitution, and can be implemented readily using BLAS2.

Remark: It is instructive to compare this method, which is called Newton interpolation, with the Vandermonde interpolation. Assuming that we use Gauss elimination to obtain the vector of unknown coefficients (see chapter 9), we see that the change of basis in the Newton approach takes us directly to the second stage of Gauss elimination, which is the forward substitution, while in the Vandermonde approach we have to essentially perform an LU decomposition of the matrix V, which is an O(N3) operation. However, the Vander-monde matrix is a special one, and its inversion can also be done in O(N2) operations (e.g.

using FFTs, see section 3.2). Thus, the two approaches discussed here are computationally equivalent.

Newton Interpolation: Recursive Algorithm

There is a nice recursive property that we can deduce from Newton’s interpolation method, and which can be used for writing compact C++ code as we shall see in the next section.

Solving for the first few coefficients, we obtain a0 = f(x0)

so we see that the coefficient

ak =F(x0, x1, . . . , xk),

that is the kth coefficient is a function of the first k function valuesf(xk). F is a function of both the xk variables and the f(xk) data (and hence, in the end, since f(x) is a function of x, then really F is just a function of the xk’s as given above).

To obtain a recursive relation for the coefficient ak we need to write the approximation in the grid

Gk0 ≡ {xi}, i= 0, . . . k ,

where the subscript denotes the starting index and the superscript denotes the ending index.

To this end, we consider the two subsets

Gk−10 ≡ {x0, x1, . . . , xk1}, and Gk1 ≡ {x1, x2, . . . , xk},

ofkgrid points each. We also denote the corresponding polynomial approximations bypk0(x), pk01(x) and pk1 formed by using the grids Gk0, Gk01 and Gk1, respectively. We then observe that

(x0−xk)pk0(x) = (x−xk)pk01(x)(x−x0)pk1(x), (3.2) as the polynomial pk0(x) passes through all the pairs

(xi, f(xi)), i= 0, . . . , k.

Next, upon substitution ofpk0(x),pk01(x) andpk1(x) in equation (3.2) by their full expansions, which are

pk0(x) = a0+a1(x−x0) +. . .+ak(x−x0). . .(x−xk−1) pk01(x) = a0+a1(x−x0) +. . .+ak1(x−x0). . .(x−xk2)

pk1(x) = b1+b2(x−x1) +. . .+bk(x−x1). . .(x−xk1).

and comparing the coefficients of highest polynomial power, xk, we obtain:

(x0−xk)ak =ak1−bk or

(x0−xk)F(x0, x1, . . . xk) =F(x0, x1, . . . xk1)− F(x1, x2, . . . xk) and therefore

F(x0, x1, . . . xk) = F(x0, . . . xk1)− F(x1, . . . xk)

x0−xk . (3.3)

We thus obtain the higher divided differences (i.e., coefficients) from the lower ones from equation (3.3).

We illustrate this procedure on a gridG20 containing three grid points (x0, x1, x2), so that F(x0) =f(x0); F(x1) =f(x1); F(x2) =f(x2),

then at the next level

F(x0, x1) = F(x0)− F(x1) x0−x1 F(x1, x2) = F(x1)− F(x2)

x1−x2 and

F(x0, x1, x2) = F(x0, x1)− F(x1, x2) x0 −x2 , and so on, for grids with more points.

3.1.2 Arrays in C++

So far, when we have discussed variables in C++, we have referred to single variables, such as the variablesmynode and totalnode presented in section 2.3.4. Now, mathematically, we just introduced a collection of variables in the form of a sequence: x0, x1, x2, ...xN. If you were to write a program which involved such a sequence of numbers, how would you declare these variables? Of course, to start with, you may use the knowledge you gained from section 2.1.2 to decide how to declare the variables. The variable declaration would look like the following (for N = 5):

double x0,x1,x2,x3,x4,x5;

This does not seem too difficult. However, imagine that you want to use 100 points!

Do you want to type x0, x1, ..., x99? And even more annoying, suppose that you want to compare the results of running a program using 50 points compared to 1000 points! Do not be dismayed; C++ has a solution to your problem! The C++ solution to this problem is the concept of arrays. In C++, you can allocate a block of memory locations using the concepts of arrays. There are two means of accomplishing this: static allocationanddynamic allocation. We will discuss both briefly.

Static Allocation of Arrays

The first means by which you can allocate an array is to staticallyallocate the array. For our purposes, we will take this to mean that prior to both compilation and execution, the size of the array is known. In the previous section, we discussed the idea of using a discrete set of points {x0, x1, . . . , xN} for interpolation. For a specific example, let us take N = 99 (so that the total number of points is 100 points), and let us assume that we want our grid points to be evenly spaced in the interval [0,1].

Software Suite

The following piece of code would statically allocate an array of 100 doubles, and would fill in those variables with their appropriate positions in the interval [0,1]:

#include <iostream.h>

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

int i;

double x[100];

double dx = 1.0/99.0;

for(i=0;i<100;i++) x[i] = i*dx;

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

cout << "x[" << i << "] = " << x[i] << endl;

}

Let us now examine in detail the statements in this program. First, notice the syntax used for allocating static arrays:

<type> <variable name>[ size ]

Here, size is the number of memory positions that you want allocated. In our example, we wanted 100 doubles to be allocated. Once the allocation is done, how do we access these variables? C++ uses [ ] for accessing variables in an array. In the above allocation, x[0] is the first element, x[1] is the second element, etc. There are several key points for you to realize:

C++ array indexing always begins at 0. Hence, the first position in an array is always the position denoted by [0].

C++ does not verify that you do not overrun an array. To overrun an array is to attempt to access a memory location which has not been allocated to the array. In the above example, trying to access x[100] would be illegal because we only allocated an array containing 100 elements (indexed 0, . . . ,99). C++ will not complain when compiling, but may cause a segmentation fault (or even far worse, it may run normally but give the wrong results!). You should be very careful not to overrun arrays!

When statically allocating arrays, you cannot use a variable for the size parameter.

Hence the following C++ code isinvalid:

int npts = 100;

double x[npts];

Your C++ compiler will complain that this is illegal! This is because it is not until the program is actually executed that the value of npts is known to the program (recall that upon executionnptsis both allocated in memory, and then intialized to the value 100). This type of operationcan be done with dynamic memory allocation, which will also be discussed below.

We can, however, index the array using variables. In the above example, we are able to iterate through all the values of the array using a forloop.

Dans le document in C++ and MPI (Page 103-108)