IN229/Simulation: Lecture 7 Knut–Andreas Lie Department of Informatics University of Oslo

advertisement
'
$
IN229/Simulation: Lecture 7
Knut–Andreas Lie
Department of Informatics
University of Oslo
April 2003
&
%
&
Au = b
This system can be solved by Gaussian elimination.
To learn more about such things, IN 227: Numerical linear algebra
Here u and b are vectors, i.e., arrays of numbers, and A is a matrix, i.e., a 2-D
array of numbers


 

1 0 0 0 ··· ··· ··· ···
0
u1
0
.
.
.
.
 1 −2 1 0
.. .. ..
..   u2   −h2f2 



 
...   u
... ...
 0 1 −2 1 . . .
  −h2f

3

 3  

...

 ... . . . . . . . . . . . .
 
...   ...
...


 

 ..





.
.
.
.
.
.
..
.. .. ..
..   ..

 .
=
 .. . .





...
...   ...
... ... ...
 .
 

.



 . .


...
...   ...
... ... ...
 .. . . . . .
 

 . .





2
 .. . . . . . . . .




−h fn−1 
un−1
1 −2 1
un
−2h − h2fn
0 ··· ··· ··· ··· ···
0 2 −2
We write the system as
Using linear algebra
'
$
IN229/Simulation: Lecture 7
1
%
'
$
IN229/Simulation: Lecture 7
Implementation
Pseudocode:
// Read parameters, allocate memory
Matrix(real) A(n,n)
Vector(real) b(n), u(n)
// Assemble matrix and left-hand side
for i=1:n
// Left boundary
if i==1
A(1,1) = 1;
b(1) = 0
// Right boundary
else if i==n
A(i,i-1) = 2; A(i,i) = -2;
b(i) = -2*h - h*h*f(x(i));
// Interior
else
A(i,i-1) = 1; A(i,i) = -2; A(i,i+1) = 1;
b(i) = -h*h*f(x(i));
end
// Solve linear system
Idea: use libraries for linear algebra.
Here, however, we will show some of the basics....
&
2
%
'
$
IN229/Simulation: Lecture 7
Solution of linear system...
Sketch of ideas for a simple 3 × 3 example:
a11x + a12y + a13z = b1
a21x + a22y + a23z = b2
a31x + a32y + a33z = b3
Here we use the system
x + 2y + z = 8
x + 4y
=9
x − 4y + 6z = 11
&
(1)
(2)
(3)
3
%
'
$
IN229/Simulation: Lecture 7
Solution of linear system...
Step 1: Forward elimination
Subtract a21/a11 = 1 times (1) from (2) and a31/a11 = 1
times (1) from (3)
x + 2y + z = 8
2y − z = 1
−6y + 5z = 3
(1’)
(2’)
(3’)
(Let aij denote new system). Subtract a32/a22 = −3 times
(2’) from (3’)
x + 2y + z = 8
2y − z = 1
2z = 6
(1”)
(2”)
(3”)
We have thus derived an upper triangular system.
&
4
%
'
$
IN229/Simulation: Lecture 7
Solution of linear system...
Step 2: Backward substitution
Solve Eq. (3”)
2z = 6
−→ z = 3
Solve Eq. (2”) using z = 3
2y − 3 = 1
−→ y = 2
Solve Eq. (1”) using y = 2 and z = 3
x + 4 + 3 = 8 −→ x = 1
This general procedure applies for a general linear
system.
&
5
%
&
LU factorization is feasible since A is tridiagonal.
• Solve Ux = y (backward substitution)
• Solve Ly = b (forward elimination)
• Compute the LU factorization: A = LU, i.e., LUx = b
Abstract formulation of Gaussian elimination:
Solution of linear system...
'
$
IN229/Simulation: Lecture 7
6
%
&
u(x) =
This gives analytical solution
1
xα+2,
α+2
α∈R
α 6= −1, −2
f (x) = −(α + 1)xα ,
Here: choose an arbitrary, twice integrable function, e.g.,
Generally important, since most computer codes contain errors in the first
attempt.
First: find a suitable test problem
Implementation contd...
'
$
IN229/Simulation: Lecture 7
7
%
&
u2 = 1 −
α+1
2
1 α+1
u(1) − u2 = α
2 α+2
1
ui = [(i − 1)h]2
2
is exact – regardless of h ⇒ perfect test problem!
For α = 0 the numerical solution
with error
u1 = 0,
Using only one interval (n = 2)
Implementation...
'
$
IN229/Simulation: Lecture 7
8
%
&
// set <A>, <u>, and <b> equal zero
A = new double*[n];
A[0] = new double[n*n];
for (i=1; i<n; i++)
A[i] = A[i-1]+n;
:
b = new double[n];
u = new double[n];
int main(int argc, char **argv)
{
:
double *b, *u;
double **A;
The complete source code (heat.C) can be downloaded from the directory
http://www.ifi.uio.no/∼in229/forelesninger/sim/heat1D/
Fragments of a C++ program in F77/C style
'
$
IN229/Simulation: Lecture 7
9
%
&
}
// output solution ...
return 0;
h = 1.0/(n-1);
for (i=0; i<n; i++) {
x = i*h;
if (i==0){
A[i][i] = 1;
b[i] = 0;
}
else if (i>0 && i<n-1) {
A[i][i-1] = 1; A[i][i] = -2; A[i][i+1] = 1;
b[i] = h*h*(alpha+1)*pow(x,alpha);
}
else {
A[i][i-1] = 2; A[i][i] = -2;
b[i] = -2*h + h*h*(alpha+1)*pow(x,alpha);
}
}
solveSys(A, u, b, n);
Fragments of code ....
'
$
IN229/Simulation: Lecture 7
10
%
'
$
IN229/Simulation: Lecture 7
Gaussian elimination
void solveSys(double **A, double *x,
double *b, int n)
{
int i, j, k;
double m;
/* Forward elimination */
for (i=0; i<n-1; i++)
for (j=i+1; j<n; j++) {
m = A[j][i]/A[i][i];
for (k=i; k<n; k++)
A[j][k] -= m*A[i][k];
b[j] -= m*b[i];
A[j][i] = -m;
}
/* Backward substitution */
for (i=n-1; i>=0; i--) {
x[i] = b[i];
for (k=i+1; k<n; k++)
x[i] -= A[i][k]*x[k];
x[i] /= A[i][i];
}
return;
}
&
11
%
'
$
IN229/Simulation: Lecture 7
Plotting the solution
0.16
u, n=50, alpha=5
0.14
0.12
0.1
0.08
0.06
0.04
0.02
0
-0.02
0
0.2
0.4
0.6
0.8
1
unix> gnuplot
G N U P L O T
.......
Terminal type set to ’x11’
gnuplot> plot ’u.dat’ title ’u, n=50, alpha=5’
gnuplot> quit
&
12
%
&
Save memory and CPU-time by utilizing the tridiagonal structure.
• number of operations: O(n3)
• storage requirement: n2 real numbers
• is designed for a general dense matrix
Gaussian elimination:
ai,i+1
A is tridiagonal, i.e., the only nonzero entries are ai,i−1, ai,i, and
Observation:
Evaluation of algorithm
'
$
IN229/Simulation: Lecture 7
13
%
&
Linear systems arising from the discretization of differential equations typically
contain a lot of zeros (as we saw above). Gaussian elimination therefore
performs a lot of unnecessary computions. Generally, one therefore prefers
methods that utilize the special structure of the linear system.
−→ number of operations is O(n)!
• backward substitution: only need to substitute values along upper
diagonal
• forward elimination: only need to eliminate lower diagonal
• in Gaussian elimination:
• nonzero elements: 3n − 2
Tridiagonal matrix:
Tridiagonal matrices
'
$
IN229/Simulation: Lecture 7
14
%
&
// A[i][i-1]
// A[i][i ]
// A[i][i+1]
// set <A[mcp]>, <u>, and <b> equal zero
Am = new double[n];
Ac = new double[n];
Ap = new double[n];
:
b = new double[n];
u = new double[n];
int main(int argc, char **argv)
{
:
double *b, *u;
double **A;
The complete source code (heatTri.C) can be downloaded from the directory
http://www.ifi.uio.no/∼in229/forelesninger/sim/heat1D/
Tridiagonal matrix using F77/C style
'
$
IN229/Simulation: Lecture 7
15
%
&
}
// output solution ...
return 0;
h = 1.0/(n-1);
for (i=0; i<n; i++) {
x = i*h;
if (i==0){
Am[i] = 0; Ac[i] = 1; Ap[i] = 0;
b[i] = 0;
}
else if (i>0 && i<n-1) {
Am[i] = 1; Ac[i] = -2; Ap[i] = 1;
b[i] = h*h*(alpha+1)*pow(x,alpha);
}
else {
Am[i] = 2; Ac[i] = -2; Ap[i] = 0;
b[i] = -2*h + h*h*(alpha+1)*pow(x,alpha);
}
}
solveSys(Am, Ac, Ap, u, b, n);
Fragments of code ....
'
$
IN229/Simulation: Lecture 7
16
%
'
$
IN229/Simulation: Lecture 7
Gaussian elimination
void solveSys(double *Am, double *Ac,
double *Ap, double *x,
double *b, int n)
{
int i;
double m;
/* Forward elimination */
for (i=1; i<n; i++) {
m = Am[i]/Ac[i-1];
Am[i] = -m;
Ac[i] -= m*Ap[i-1];
b[i] -= m*b[i-1];
}
/* Backward substitution */
i = n-1;
x[i] = b[i]/Ac[i];
for ( i--; i>=0; i--)
x[i] = (b[i] - Ap[i]*x[i+1])/Ac[i];
return;
}
&
17
%
'
$
IN229/Simulation: Lecture 7
Evaluation of program
Advantages:
• Reduced memory requirement
n2 double + n double∗ + 1 double∗∗
−→ 3n double + 3 double∗
• Reduced CPU requirements:
Table: CPU time in seconds for grid with n unknowns
125 250 500 1000 2000
heat
0.07 0.6 5.9 48.9 391.5
heatTri 0.01 0.01 0.01 0.03 0.05
Disadvantages:
• We had to rewrite major portions of the code
• Storage scheme for tridiagonal matrix is shown explicit
So, let us elaborate a bit on this...
&
18
%
'
$
IN229/Simulation: Lecture 7
Programming with matrices
• What is a matrix?
A well defined mathematical quantity, containing a
table of numbers and a set of legal operations
• How do we program with matrices?
Do standard arrays in any computer language give
good enough support for matrices?
No! We need a jungle of formats for different types
of matrices:
Why? Efficiency of numerical method and memory
requirements depend strongly upon the structure of
the matrix and the storage scheme.
&
19
%
'
$
IN229/Simulation: Lecture 7
An example: heat condution in 2-D
• Matrix for the discretization of −∇2 = f :
0
5
10
15
20
25
30
35
0
5
10
15
20
nz = 166
25
30
35
• Only 5n out of n2 entries are nonzero.
• Storage: store only nonzero entries
&
20
%
'
$
IN229/Simulation: Lecture 7
How to store sparse matrices?

a1,1 0
0 a1,4 0
 0 a a
0 a2,5

2,2
2,3

A =  0 a3,2 a3,3 0
0

0 a4,4 a4,5
 a4,1 0
0 a5,2 0 a5,4 a5,5







• Only a small fraction of the entries are nonzero
• Why bother?
Utilizing sparsity is essential for computational
efficiency!
• Implementation:
A = (a1,1, a1,4, a2,2, a2,3, a2,5, . . .
irow = (1, 3, 6, 8, 11, 14),
jcol = (1, 4, 2, 3, 5, 2, 3, 1, 4, 5, 2, 4, 5).
⇒ more complicated data structures
⇒ more complicated programs
• Obvious idea: hide details of storage and
mathematical operations inside a class...
&
21
%
'
$
IN229/Simulation: Lecture 7
The jungle of matrix formats
• Suppose we want to hide details of storage in a class
• But as we saw above, there are a lot of formats:
- dense matrix
- banded matrix
- tridiagonal matrix
- general sparse matrix
- structured sparse matrix
- diagonal matrix
- finite difference stencil as matrix
- ...
• The efficiency of numerical algorithms is often strongly
dependent on the matrix storage scheme
• Who is interested in knowing all details of the data
structures? - Very few!
• Scenario: one often has has to try different storage
forms to get maximal code efficiency.
&
22
%
'
$
IN229/Simulation: Lecture 7
Object-oriented realization
Matrix
MatDense
MatSparse
MatTriDiag
MatBanded
• Matrix = object
• Generic interface in base class Matrix: define
operations, no data
• Specific realization in subclasses: implementation of
storage and member functions
• Generic programming in user code via base class:
Matrix& M;
M.prod(x,y);
// y=M*x
i.e., we need not know the structure of M, only that it
refers to some concrete subclass object.
• Member functions are virtual functions
&
23
%
&
MatBand
Mat
MatSimple
MatTri
MatStructSparse
MatSimplest
Matrix
MatDiag
Matrices in Diffpack
MatSparse
'
$
IN229/Simulation: Lecture 7
24
%
&
More about OON later.... Now: back to the heat equation!
⇒ Object-oriented numerics: balance between efficiency and OO
techniques
• Adjusted picture:
When indexing a matrix, one needs to know its data storage
structure because of efficiency, but in most of the code one can
work with the base class
• Object-oriented programming do wonderful things, but might be
inefficient
Bad news...
'
$
IN229/Simulation: Lecture 7
25
%
'
$
IN229/Simulation: Lecture 7
Variable coefficients
Mathematical problem:
d
du = f (x)
−
λ(x)
dx
dx
Have you seen the left-hand side earlier...?
Numerical approximation
d
du λi+1/2(ui+1 − ui) − λi−1/2(ui − ui−1)
λ(x)
≈
dx
dx
h2
Potential problem: λi+1/2 not evaluated at grid point.
Solution: use some kind of interpolation
• Arithmetic mean: λi+ 12 = 12 (λi + λi+1 )
1
1
1
1
• Harmonic mean: λ 1 = 2 λi + λi+1
i+ 2
• Geometric mean: λi+ 21 = (λi λi+1 )1/2
When this is resolved we get a linear system (as before)
Au = b
&
26
%
'
$
IN229/Simulation: Lecture 7
Nonlinear heat conduction
• Heat conduction typically depends upon the
temperature
du
d
−
λ(u)
= f (x), 0 < x < 1
dx
dx
This is a nonlinear differential equation.
• Straightforward discretization gives
u1 = 0
λ(ui+ 1 )(ui+1 − ui) − λ(ui− 1 )(ui − ui−1) = −h2fi
2
2
2λ(un)(un−1 − un) = −2hλ(un+ 1 ) − h2fn
2
• Since λ(ui+ 1 ) will depend upon ui and ui+1, this is a set
2
of nonlinear algebraic equations
A(u)u = b(u)
i.e., the coefficients are unknown quantities....
&
27
%
'
$
IN229/Simulation: Lecture 7
Successive substitution
Idea: use the following simple algorithm
Guess a solution u0.
Repeat
solve A(un)un+1 = b(un)
until difference of un and un+1 is small
Each equation is now a linear equation with variable
coefficients.
Pros: may reuse previous code by inserting evaluations
of the coefficient λ(ui+ 1 )
2
Cons: slow convergence
(More advanced methods in e.g., MA-IN 127, IN-NMFPD)
&
28
%
'
$
IN229/Simulation: Lecture 7
Implementation
Reuse old program HeatTri with:
• Loop around system generation and solution
• Two arrays uk and ukm
• Initial guess in ukm
• New auxiliary variables (for iteration etc.)
• Function lambda to evaluate λ(u)
• Update A[mcp] and b for each step
• Check for termination upon convergence
• Set ukm equal uk before new iteration
&
29
%
'
$
IN229/Simulation: Lecture 7
Implementation – pseudocode
// Read parameters, allocate memory
Matrix(real) A(n,n)
Vector(real) b(n), uk(n), ukm(n)
// Make initial guess
// Iteration loop
Repeat
// Assemble matrix and left-hand side
for i=1:n
// Left boundary
if i==1
:
// Right boundary
else if i==n
:
// Interior
else
l1 = lambda( u(i-1) );
l2 = lambda( u(i)
);
l3 = lambda( u(i+1) );
A(i,i-1) = 0.5*(l1 + l2);
A(i,i)
= -0.5*(l1 + 2*l2 + l3);
A(i,i-1) = 0.5*(l2 + l3);
end
// Solve linear system
// Check termination criterion
ukm = uk;
// for next iteration
until termination
&
30
%
'
$
IN229/Simulation: Lecture 7
The importance of software design
• PDE simulator: 50 000+ code lines
• Maintainability important
• Should be easy to extend
• Should be easy to use/understand
• Abstractions close to mathematical language are
needed
For PDEs, the discretization consists of
• The unknown u as a scalar or vector field
• The field is defined over a grid
Primarily a field consists of values at discrete grid points,
but may also contain interpolation rules (e.g., for FEM
methods).
&
31
%
&
x∈Ω
• grid: discrete Ω
• scalar fields, finite difference type: discrete u
• scalar fields, arbitrary representation: λ(x), f (x) (explicit functions,
discrete fields)
• Principal numerical quantities:
• domain: Ω
• scalar fields: λ(x), u(x), f (x)
• Principal mathematical quantities:
−∇ · [λ(x)∇u(x)] = f (x),
• Consider the PDE (describing e.g., heat conduction):
Grid and field abstractions
'
$
IN229/Simulation: Lecture 7
32
%
&
0
1
0
1
What does a grid typically look like?
'
$
IN229/Simulation: Lecture 7
33
%
&
• possible to write code that is (almost) independent of the number of
space dimensions (i.e., easy to go from 1D to 2D to 3D!)
• big programs: fundamental
• finite element methods: important
• finite difference methods: minor
• shorter code, closer to the mathematics
• Gain:
• collect field information in a field class
• collect grid information in a grid class
• Obvious ideas:
Programming considerations
'
$
IN229/Simulation: Lecture 7
34
%
&
d=3 [0,1]x[-2,2]x[0,10]
indices [1:20]x[-20:20]x[0:40]
d=1 domain: [0,1], index [1:20]
• initialization from input string, e.g.,
• lattice with uniform partition in d dimensions
• Grid represented by GridLattice
• MyArray is a class implementing user-friendly arrays in one and more
dimensions (i.e., an extension of our earlier MyArray)
• a set of point values, MyArray
• a grid of type GridLattice
• Field represented by FieldLattice:
Assume a finite difference method (FDM):
Grids and fields for FDM
'
$
IN229/Simulation: Lecture 7
35
%
&
// other tasks:
const int nx = g.getDivisions(1);
const int ny = g.getDivisions(2);
const int dx = g.Delta(1);
const int dy = g.Delta(2);
//
//
//
//
number of grid cells in x dir
number of grid cells in y dir
grid spacing in x dir
grid spacing in y dir
GridLattice g;
// declare an empty grid
g.scan("d=2 [0,1]x[0,2] [1:10]x[1:40]"); // initialize g
const int i0 = g.getBase(1);
// start of first index
const int j0 = g.getBase(2);
// start of second index
const int in = g.getMaxI(1);
// end of first index
const int jn = g.getMaxI(2);
// end of second index
int i,j;
for (i = i0; i <= in; ++i) {
for (j = i0; j <= jn; ++j) {
std::cout << "grid point (" << i << ’,’ << j
<< ") has coordinates (" << g.getPoint(1,i)
<< ’,’ << g.getPoint(2,j) << ")\n";
}
Example of how we want to program:
The GridLattice class
'
$
IN229/Simulation: Lecture 7
36
%
&
The static keyword means that MAX_DIMENSIONS is a “global” constant
shared by all GridLattice objects.
// variables defining the size of the grid
double min[MAX_DIMENSIONS];
// min coordinate values
//
in each dimension
double max[MAX_DIMENSIONS];
// max coordinate values
//
in each dimension
int division[MAX_DIMENSIONS]; // number of points
//
in each dimension
int dimensions;
// number of dimensions
class GridLattice
{
// currently limited to two dimensions
static const int MAX_DIMENSIONS = 2;
• For unstructured grids: a carefully designed data structure is vital to obtain
code efficiency
• Trivial in our case, use: xmin, xmax, ymin, ymax, and nx, ny .
Data representation:
The GridLattice class..
'
$
IN229/Simulation: Lecture 7
37
%
&
int getDivisions(int i) const; // get the number of points in
// each dimension
double xMin(int dimension) const;
double xMax(int dimension) const;
int getNoSpaceDim () const;
public:
GridLattice();
GridLattice(int nx, int ny, double xmin_, double xmax_,
double ymin_, double ymax_);
• Accessors – access to internal data structure
• Initialization – through the scan function
• Constructors
Member functions:
The GridLattice class...
'
$
IN229/Simulation: Lecture 7
38
%
&
// get base values
// upper limit of array
friend std::ostream& operator<<(std::ostream&, GridLattice&);
void scan(const std::string& init_string);
// scan parameters from string
int getBase(int dimension) const;
int getMaxI(int dimension) const;
Mutators, i.e., functions for setting internal data members, are not
implemented here. Examples: setBase(..), setMaxI(..), etc.
The friend keyword enables operator« to access private data in the
GridLattice object.
};
// get the total number of
// points in the grid.
double Delta(int dimension) const;
double getPoint(int dimension, int index);
int getNoPoints() const;
The GridLattice class...
'
$
IN229/Simulation: Lecture 7
39
%
&
Inline functions means that the function call can be optimized away by the
compiler.
}
return return_value;
int GridLattice:: getNoPoints() const
{
int return_value = 1;
for(int i = 0; i != dimensions; ++i)
return_value *= division[i];
inline int GridLattice:: getDivisions(int i) const
{ return division[i-1]; }
double GridLattice:: xMax(int dimension) const
{ return max[dimension - 1]; }
double GridLattice:: xMin(int dimension) const
{ return min[dimension - 1]; }
The GridLattice class...
'
$
IN229/Simulation: Lecture 7
40
%
&
inline double GridLattice:: getPoint(int dimension, int index)
{
#ifdef NO_NESTED_INLINES
return min[dimension-1] + ((max[dimension-1]- min[dimension-1])
/ double(division[dimension-1]))*(index - 1);
#else
return min[dimension-1] + (Delta(dimension) * (index - 1));
#endif
}
Some compilers do not allow nested inlines. To come around this, we can use a preprocessor macro:
inline double GridLattice:: getPoint(int dimension, int index)
{
return min[dimension-1] + (Delta(dimension) * (index - 1));
}
inline double GridLattice:: Delta(int dimension) const
{
return (max[dimension-1] - min[dimension-1])
/ double(division[dimension-1]);
}
Nested inline functions:
The GridLattice class...
'
$
IN229/Simulation: Lecture 7
41
%
&
is.ignore(1, ’=’);
// get the dimensions
is >> dimensions;
if (dimensions < 1 || dimensions > MAX_DIMENSIONS) {
cerr << "GridLattice::scan() -- illegal dimensions "
<< dimensions << endl;
cerr << "
MAX_DIMENSIONS is set to "
<< MAX_DIMENSIONS << endl;
exit(1);
:
// ignore "d="
is.ignore(1, ’d’);
void GridLattice:: scan(const string& init_string)
{
using namespace std; // allows dropping std:: prefix
istrstream is(init_string.c_str());
To parse the string, we use functionality in the C++ standard library:
g.scan("d=2 [0,1]x[0,2] [1:10]x[1:40]");
The scan function is typically called as follows
The GridLattice class...
'
$
IN229/Simulation: Lecture 7
42
%
&
We have chosen to initialize internal date also for the empty constructor to
avoid errors.
GridLattice:: GridLattice()
{
dimensions = 2;
int i;
for (i = 1; i <= MAX_DIMENSIONS; ++i) {
min[i] = 0; max[i] = 1; division[i] = 2;
}
}
GridLattice:: GridLattice(int nx, int ny,
double xmin, double xmax,
double ymin, double ymax)
{
dimensions = 2;
max[0] = xmax;
max[1] = ymax;
min[0] = xmin;
min[1] = ymin;
division[0] = nx; division[1] = ny;
}
We have two different constructors:
The GridLattice class...
'
$
IN229/Simulation: Lecture 7
43
%
&
};
{ return fieldname; }
std::string name() const
// set the values of the field
void values(MyArray<real>& new_array);
{ return *grid_lattice; }
{ return *grid_lattice; }
// enable access to the grid:
GridLattice& grid()
const GridLattice& grid() const
// enable access to grid-point values:
MyArray<real>& values()
{ return *grid_point_values; }
const MyArray<real>& values() const { return *grid_point_values; }
public:
// make a field from a grid and a fieldname:
FieldLattice(GridLattice& g, const std::string& fieldname);
class FieldLattice
{
public:
Handle<GridLattice>
grid_lattice;
Handle< MyArray<real> > grid_point_values;
std::string
fieldname;
The FieldLattice class
'
$
IN229/Simulation: Lecture 7
44
%
&
also Hm, H, and Hp).
Example: For the wave equation um, u, and up may share the same grid (and possibly
• Using a Handle<GridLattice> object instead of a GridLattice object,
means that a grid can be shared among several fields.
• The Handle<> construction is a smart pointer, implementing reference
counting and automatic deallocation (garbage collection).
• We use a parameter real, which equals float or double (by default).
• Inline functions are obtained by implementing the function body inside the
class declaration.
A few remarks on FieldLattice
'
$
IN229/Simulation: Lecture 7
45
%
&
• Pointers are bug no. 1 in C/C++ ...
object?
Example: Suppose 5 fields point to the same grid — when can we safely remove the grid
• How does one determine when memory is no longer in use?
• Codes with memory leakage slowly eat up the memory and slow down
computations
• Lack of garbage collection (automatic clean-up of memory that is no longer
in use) means that manual deallocation is important
• Dynamic memory in C/C++ means that pointers are needed
Observations:
Smart pointers (handles)
'
$
IN229/Simulation: Lecture 7
46
%
&
• several fields can safely share one grid
• automatic garbage collection
• negligible overhead
Advantages:
• Introduce reference counting: count the number of times a smart pointer
“uses a pointer” (i.e., points to a given object).
• Avoid explicit deallocation
Solution to problems:
Smart pointers — reference counting
'
$
IN229/Simulation: Lecture 7
47
%
&
// x points to new X object
// send object (not handle)
// alternative syntax
x.rebind (new X());
someFunc (x());
someFunc (*x);
Details of class Handle is given in [R&L]
// given Handle(X) y:
x.rebind (y());
// x points to y’s object
x.rebind (*y);
// alternative syntax
// NULL pointer
Handle(X) x;
Implementing handles, we can achive the following:
Smart pointers...
'
$
IN229/Simulation: Lecture 7
48
%
&
void FieldLattice:: values(MyArray<real>& new_array)
{ grid_point_values.rebind(&new_array); }
FieldLattice:: FieldLattice(GridLattice& g,
const std::string& name_)
{
grid_lattice.rebind(&g);
// allocate the grid_point_values array:
if (grid_lattice->getNoSpaceDim() == 1)
grid_point_values.rebind(
new MyArray<real>(grid_lattice->getDivisions(1)));
else if (grid_lattice->getNoSpaceDim() == 2)
grid_point_values.rebind(new MyArray<real>(
grid_lattice->getDivisions(1),
grid_lattice->getDivisions(2)));
else
; // three-dimensional fields are not yet supported...
fieldname = name_;
}
The FieldLattice class...
'
$
IN229/Simulation: Lecture 7
49
%
&
class MySim
{
protected:
// grid and field objects
// PDE dependent parameters
public:
void scan();
// read input and init
void solveProblem();
void resultReport();
};
• Typical look (for a static problem):
⇒ enables coupling to optimization, automatic parameter analysis etc.
⇒ easy to extend/modify solver
⇒ easy to combine solvers (systems of PDEs)
• The PDE solver is a class itself
Simulator classes
'
$
IN229/Simulation: Lecture 7
50
%
&
class Heat1D1
{
protected:
// data items visible in subclasses
MatTri<real>
A;
// the coefficient matrix
MyArray<real>
b;
// the right-hand side
Handle<GridLattice> grid; // 1D grid
FieldLattice
u;
// the discrete solution
real
alpha; // parameter in the test problem
public:
Heat1D1() {}
~Heat1D1() {}
void scan ();
// read input, set size of A, b and u
void solveProblem ();
// compute A, b; solve Au=b
void resultReport ();
// write and plot results
};
#endif
#ifndef Heat1D1_h_IS_INCLUDED
#define Heat1D1_h_IS_INCLUDED
:
Heat.C revisited — OO implementation
'
$
IN229/Simulation: Lecture 7
51
%
&
CommandLineArgs::read is described closer in [R&L].
./heat1D1 -a 0.1 -n 100
The above routine reads input from the command line:
}
u.rebind (new GridLattice(*grid, "u"));
A.redim(n);
// set size of tridiagonal matrix A
b.redim(n);
// set size of vector b
CommandLineArgs::read ("-a", alpha, 0.0);
grid.rebind (new GridLattice());
sprintf(gridstr,"d=1 [0,1] [1:%d]",n);
grid->scan(gridstr);
int n; // no of intervals in the domain (0,1)
// read n from the command line, a la app -n 10
CommandLineArgs::read("-n", n, 5);
void Heat1D1:: scan ()
{
char gridstr[30];
Reading input
'
$
IN229/Simulation: Lecture 7
52
%
&
}
// Gaussian elimination
A.factLU();
A.forwBack(b,u.values());
x = grid->getPoint(1,n);
A(n-1,n) = 2; A(n,n) = -2;
b(n) = - 2*h + h*h*(alpha+1)*pow(x,alpha);
for (i = 2; i < n; i++) {
x = grid->getPoint(1,i);
A(i-1,i) = 1; A(i,i) = -2; A(i,i+1) = 1;
b(i) = h*h*(alpha+1)*pow(x,alpha);
}
i = 1;
A(1,1) = 1; A(1,2) = 0;
b(1) = 0;
void Heat1D1:: solveProblem ()
{
A.fill(0.0); b.fill(0.0);
const int n = b.size(); // alternative: grid->getMaxI(1)
const real h = grid->Delta(1);
real x; int i;
Solving the problem
'
$
IN229/Simulation: Lecture 7
53
%
&
So far, very little has been gained, but let us also revisit the nonlinear case..
int main(int argc, const char* argv[])
{
Heat1D1 simulator;
simulator.scan ();
simulator.solveProblem ();
simulator.resultReport ();
return 0; // success
}
#include <Heat1D1.h>
The main program
'
$
IN229/Simulation: Lecture 7
54
%
'
$
IN229/Simulation: Lecture 7
−(λ(u)u0)0 = 0 revisited
We can implement different λ() functions objects (or
functors) in a class hierarchy.
• First the base class
class LambdaFunc : public HandleId
{
public:
virtual real lambda (real u)=0;
virtual real exactSolution (real x)=0;
virtual void scan () =0;
virtual string formula ()=0;
};
No data and all functions pure virtual, thus defining
only an interface (providing access all functions).
• Then a particular realisation in a subclass
class Lambda1 : public LambdaFunc
{
real m;
public:
Lambda1() {}
virtual real lambda (real u)
{ return pow(u,m); }
virtual real exactSolution (real x)
{ return pow(x,1/(m+1)); }
virtual void scan ()
{ CommandLineArgs::read("-m",m,0.0); }
virtual String formula () { return "u^m"; }
};
Notice that the parameter m is a member of the
subclass and not the baseclass.
&
55
%
&
class Heat1Dn1
{
protected:
MatTri<real>
A;
MyArray<real>
b;
Handle<GridLattice> grid;
FieldLattice
uk;
FieldLattice
ukm;
real
m;
real
epsilon;
Handle<LambdaFunc> lambda;
public:
Heat1Dn1() {}
~Heat1Dn1() {}
void scan ();
//
void makeSystem();
//
void solveSystem();
//
void solveProblem ();
//
void resultReport ();
//
};
data items visible in subclasses
the coefficient matrix
the right-hand side
1D grid
the discrete solution u^k
the discrete solution u^{k-1}
parameter in the test problem
tolerance in nonlinear iteration
specific lambda function
read input, set size of A, b, and u
compute A, b;
solve Au^k=b
nonlinear iteration loop
write numerical error (if possible)
//
//
//
//
//
//
//
//
//
The Heat1Dn1 class
'
$
IN229/Simulation: Lecture 7
56
%
&
// initialize function-specific parameters
lambda->scan();
// List choices of lambda functions
int lambda_tp;
CommandLineArgs::read("-N", lambda_tp, 1);
if (lambda_tp == 1)
lambda.rebind (new Lambda1());
else if (lambda_tp == 2)
lambda.rebind (new Lambda2());
else
std:cerr << "Heat1Dn::scan: wrong -N "
<< lambda_tp << "%d option." << endl;
Initializing the lambda object:
Using lambda in the code
'
$
IN229/Simulation: Lecture 7
57
%
&
lambda->exactSolution(grid->getPoint(1,i));
Computing the exact solution (if available):
lambda->lambda(ukm.values()(i-1));
Evaluating lambda:
Using lambda in the code...
'
$
IN229/Simulation: Lecture 7
58
%
&
ut(x, 0) = 0
Working with class hierarchies and virtual functions is really the point that qualifies this to be called
object-oriented programming as opposed to “programming with objects”...
• functor hierarchies1 for the known functions H(x) and I(x)
• a time integration parameter object TimePrm
• a field of type FieldLattice for the depth Hi,j
−
• three fields of type FieldLattice for u+
i,j , ui,j , and ui,j
• a grid object of type GridLattice
What are the natural objects here?
∂ 2u
= ∇ · H(x)∇u ,
u(x, 0) = I(x),
∂t2
The solution algorithm was presented in Lecture 5.
We wish to solve:
Wave equation revisited
'
$
IN229/Simulation: Lecture 7
59
%
&
//
//
//
//
current time value
time step size
stop time
time step counter
};
{ return time_; }
{ return delta; }
{ return (time >= stop) ? true : false; }
}
int getTimeStepNo() { return timestep; }
void increaseTime() { time_ += delta; ++timestep; }
bool finished()
void initTimeLoop() { time_ = 0; timestep = 0;
double time()
double Delta()
public:
TimePrm(double start, double delta, double stop)
: time_(start), delta(delta), stop(stop)
{ initTimeLoop(); }
class TimePrm
{
double time_;
double delta;
double stop;
int
timestep;
The TimePrm class
'
$
IN229/Simulation: Lecture 7
60
%
&
grid;
up;
u;
um;
lambda;
tmp;
tip;
H;
I;
lattice grid here 1D grid
solution u at time level l+1
solution u at time level l
solution u at time level l-1
variable coefficient (depth)
variable coefficient (depth)
time integr. prms: dt, t_stop
depth function
initial surface function
// perform time stepping
// dump fields to file, plot later
//
//
//
//
//
//
//
//
//
void
void
real
public:
void
void
};
// set initial conditions
// load H into lambda for efficiency
// calculate optimal timestep
// read input and initialize
// start the simulation
setIC();
setH ();
calculateDt(int func);
scan();
solveProblem();
void WAVE(FieldLattice& up, const FieldLattice& u,
const FieldLattice& um, real a, real b, real c);
void timeLoop();
void plot(bool initial);
class Wave2D1
{
Handle<GridLattice>
Handle<FieldLattice>
Handle<FieldLattice>
Handle<FieldLattice>
Handle<FieldLattice>
Handle<FieldLattice>
Handle<TimePrm>
Handle<WaveFunc>
Handle<WaveFunc>
The Wave2D1 class
'
$
IN229/Simulation: Lecture 7
61
%
&
}
// set the help variable um:
WAVE (*um, *u, *um, 0.5, 0.0, 0.5);
// fill the field for the current time period with values from the
// appropriate function.
MyArray<real>& uv = u->values();
for (int j = 1; j <= ny; j++)
for (int i = 1; i <= nx; i++)
uv(i, j) = I->valuePt(grid->getPoint(1, i),
grid->getPoint(2, j));
void Wave2D1:: setIC ()
{
const int nx = grid->getMaxI(1);
const int ny = grid->getMaxI(2);
void Wave2D1:: solveProblem ()
{
setIC();
// set initial conditions
timeLoop();
// run the algorithm
}
The Wave2D1 class...
'
$
IN229/Simulation: Lecture 7
62
%
&
}
}
plot(false);
// move handles (get ready for next step):
tmp = um; um = u; u = up; up = tmp;
WAVE (*up, *u, *um, 1, 1, 1);
while(!tip->finished()) {
tip->increaseTime();
void Wave2D1:: timeLoop ()
{
tip->initTimeLoop();
plot(true);
The Wave2D1 class...
'
$
IN229/Simulation: Lecture 7
63
%
'
$
IN229/Simulation: Lecture 7
The Wave2D1 class...
void Wave2D1:: scan ()
{
// create the grid...
grid.rebind(new GridLattice());
grid->scan(CommandLineArgs::read("-grid",
"d=2 [-10,10]x[-10,10] [1:30]x[1:30]"));
cout << (*grid) << endl;
// create new fields...
up.
rebind(new FieldLattice(*grid,
u.
rebind(new FieldLattice(*grid,
um.
rebind(new FieldLattice(*grid,
lambda.rebind(new FieldLattice(*grid,
"up"));
"u"));
"um"));
"lambda"));
// select the appropriate I and H
int func = CommandLineArgs::read("-func", 1);
if (func == 1) {
H.rebind(new GaussianBell(’H’));
I.rebind(new GaussianBell(’U’));
}
else {
H.rebind(new Flat());
I.rebind(new Plug(’U’));
}
// initialize the parameters in the functions.
H->scan();
I->scan();
// set H field and compute optimal dt
setH();
tip.rebind(new TimePrm(0, calculateDt(func),
CommandLineArgs::read("-tstop", 30.0)));
}
&
64
%
Download