' $ 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 %