Slides C++
Slides C++
Slides C++
January 2006
Outline
1 Intro to C++ programming
About C and C++
Introductory C++ example
Manipulate data files
Matrix-vector product
The C preprocessor
Exercises
About classes in C++
A simple class
2 Class programming
Class Complex
A vector class
Standard Template Library
3 Efficiency; C++ vs. F77
4 Object-Oriented Numerical Programming
OOP example: ODE solvers
Classes for PDEs
H. P. Langtangen Introduction to C++ (and C) Programming
Intro Classes Efficiency OOP
Contents
Required background
Teaching philosophy
Intensive course:
Lectures 9-12
Hands-on training 13-16
Learn from dissecting examples
Get in touch with the dirty work
Get some overview of advanced topics
Focus on principles and generic strategies
Continued learning on individual basis
Recommended attitude
C++
nicer syntax:
- declare variables wherever you want
- in/out function arguments use references (instead of pointers)
classes for implementing user-defined data types
a standard library (STL) for frequently used data types (list,
stack, queue, vector, hash, string, complex, ...)
object-oriented programming
generic programming, i.e., parameterization of variable types
via templates
exceptions for error handling
C is a subset of C++
Some guidelines
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
main(argc,argv)
int argc;
char **argv;
{
Display *dpy; /* X server connection */
Window win; /* Window ID */
GC gc; /* GC to draw with */
XFontStruct *fontstruct; /* Font descriptor */
unsigned long fth, pad; /* Font size parameters */
unsigned long fg, bg, bd; /* Pixel values */
unsigned long bw; /* Border width */
XGCValues gcv; /* Struct for creating GC */
XEvent event; /* Event received */
XSizeHints xsh; /* Size hints for window manager */
char *geomSpec; /* Window geometry string */
XSetWindowAttributes xswa; /* Temp. Set Window Attr. struct */
if ((dpy = XOpenDisplay(NULL)) == NULL) {
fprintf(stderr, "%s: cant open %s\en", argv[0],
XDisplayName(NULL));
exit(1);
}
/*
* Loop forever, examining each event.
*/
while (1) {
XNextEvent(dpy, &event);
if (event.type == Expose && event.xexpose.count == 0) {
XWindowAttributes xwa;
int x, y;
while (XCheckTypedEvent(dpy, Expose, &event));
if (XGetWindowAttributes(dpy, win, &xwa) == 0)
break;
x = (xwa.width - XTextWidth(fontstruct, STRING,
strlen(STRING))) / 2;
y = (xwa.height + fontstruct->max_bounds.ascent
- fontstruct->max_bounds.descent) / 2;
XClearWindow(dpy, win);
XDrawString(dpy, win, gc, x, y, STRING, strlen(STRING));
}
}
exit(1);
}
C++/Qt implementation
#include <qapplication.h>
#include <qlabel.h>
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
QLabel hello("Hello world!", 0);
hello.resize(100, 30);
a.setMainWidget(&hello);
hello.show();
return a.exec();
}
Python implementation
#!/usr/bin/env python
from Tkinter import *
root = Tk()
Label(root, text=Hello, World!,
foreground="white", background="black").pack()
root.mainloop()
THE textbook on C
Learning by doing:
Usage:
./hw1.app 2.3
What to learn:
1 store the first command-line argument in a floating-point
variable
2 call the sine function
3 write a combination of text and numbers to standard output
The code
Dissection (1)
Dissection (2)
Dissection (3)
An interactive version
Let us ask the user for the real number instead of reading it
from the command line
std::cout << "Give a real number:";
double r;
std::cin >> r; // read from keyboard into r
double s = sin(r);
// etc.
C uses stdio.h for I/O and functions like printf for output;
C++ can use the same, but the official tools are in iostream
(and use constructions like std::cout << r)
Variables can be declared anywhere in C++ code; in C they
must be listed in the beginning of the function
Then link:
unix> g++ -o executable_file -L/some/libdir -L/some/other/libdir \
*.o -lmylib -lyourlib -lstdlib
Makefiles
File: src/C/hw/hw-error.c
The number 2.3 was not read correctly...
argv[1] is the string "2.3"
r is not 2.3 (!)
The program compiled and linked successfully!
Remedy
The warning tells us that the compiler cannot see the declaration
of atof, i.e., a header file with atof is missing
Program structure
#include <iostream>
#include <fstream>
#include <iomanip>
#include <math.h>
double myfunc(double y)
{
if (y >= 0.0) {
return pow(y,5.0)*exp(-y);
} else {
return 0.0;
}
}
int main (int argc, char* argv[])
{
char* infilename; char* outfilename;
/* abort if there are too few command-line arguments */
if (argc <= 2) {
std::cout << "Usage: " << argv[0] << " infile outfile" << \n;
exit(1);
} else {
infilename = argv[1]; outfilename = argv[2];
}
Print to file:
ofile << x << " " << y << \n;
i.e.
i=[integer in a field of width 2 chars]
r=[float/double written as compactly as possible]
s=[float/double written with 5 decimals, in scientific notation,
in a field of width 12 chars]
method=[text]
This is accomplished by
printf("i=%2d, r=%g, s=%12.5e, method=%s\n", i, r, s, method);
char**
some string
char*
char*
abc
char*
some running text ....
.
.
.
NULL
Type conversion
Program structure
#include <stdio.h>
#include <math.h>
double myfunc(double y)
{
if (y >= 0.0) {
return pow(y,5.0)*exp(-y);
} else {
return 0.0;
}
}
C file opening
Open a file:
FILE *somefile;
somefile = fopen("somename", "r" /* or "w" */);
if (somefile == NULL) {
/* unsuccessful open, write an error message */
...
}
Print to file:
fprintf(ofile, "Here is some text: %g %12.5e\n", x, y);
What to learn
Note: matrices are stored row wise; the column index should
vary fastest
Recall that in Fortran, matrices are stored column by column
Typical loop in Fortran (2nd index in outer loop):
for (j=0; j<N; j++) {
for (i=0; i<M; i++) {
A[i][j] = f(i,j) + 3.14;
}
}
The length of arrays can be decided upon at run time and the
necessary chunk of memory can be allocated while the
program is running
Such dynamic memory allocation is error-prone!
You need to allocate and deallocate memory
C++ programmers are recommended to use a library where
dynamic memory management is hidden
We shall explain some details of dynamic memory
management; you should know about it, but not necessarily
master the details
Storage of vectors
Storage of matrices
double**
. . . . . .
.
. double*
.
Allocation of a matrix in C
double**
. . . . . .
.
. double*
.
Deallocation of a matrix in C
C++ style:
delete [] A[0];
delete [] A;
MyArray<double> A, x, b;
int n;
if (argc >= 2) {
n = atoi(argv[1]);
} else {
n = 5;
}
A.redim(n,n); x.redim(n); b.redim(n);
int i,j;
for (j=1; j<=n; j++) {
x(j) = j/2.0;
for (i=1; i<=n; i++) {
A(i,j) = 2.0 + double(i)/double(j);
}
}
Computation:
double sum;
for (i=1; i<=n; i++) {
sum = 0.0;
for (j=1; j<=n; j++) {
sum += A(i,j)*x(j);
}
b(i) = sum;
}
Subprograms in C++
f takes a copy x of x1
Call by reference
Problem setting: How can changes to a variable inside a function
be visible in the calling code?
C applies pointers,
int n; n=8;
somefunc(&n); /* &n is a pointer to n */
void somefunc(int *i)
{
*i = 10; /* n is changed to 10 */
...
}
A C++ function
Subprograms in C
In function calls:
int n; n=8;
somefunc(&n);
void somefunc(int *i) /* i becomes a pointer to n */
{
/* i becomes a copy of the pointer to n, i.e.,
i also points to n.
*/
*i = 10; /* n is changed to 10 */
...
}
Pointer arithmetics
Preprocessor directives
Preprocessor if-tests
Macros
You can first run the preprocessor on the program files and
then look at the source code (with macros expanded):
unix> g++ -E -c mymacros.cpp
or in the code:
#define real float
Write a function
double trapezoidal(userfunc f, double a, double b, int n)
Zb n2
!
f (a) f (b) X ba
f (x)dx h + + f (a + ih) , h= .
2 2 n1
a i =1
Binary format
Traditional programming
subroutines/procedures/functions
data structures = variables, arrays
data are shuffled between functions
Problems with procedural approach:
Mathematical problem:
Matrix-matrix product: C = MB
Matrix-vector product: y = Mx
Points to consider:
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?
// given integers p, q, j, k, r
MatDense M(p,q); // declare a p times q matrix
M(j,k) = 3.54; // assign a number to entry (j,k)
MatDense B(q,r), C(p,r);
Vector x(q), y(p); // vectors of length q and p
C=M*B; // matrix-matrix product
y=M*x; // matrix-vector product
M.prod(x,y); // matrix-vector product
Observe that
we hide information about array sizes
we hide storage structure (the underlying C array)
the computer code is as compact as the mathematical
notation
class MatDense
{
private:
double** A; // pointer to the matrix data
int m,n; // A is an m times n matrix
public:
// --- mathematical interface ---
MatDense (int p, int q); // create pxq matrix
double& operator () (int i, int j); // M(i,j)=4; s=M(k,l);
void operator = (MatDense& B); // M = B;
void prod (MatDense& B, MatDense& C); // M.prod(B,C); (C=M*B)
void prod (Vector& x, Vector& z); // M.prod(y,z); (z=M*y)
MatDense operator * (MatDense& B); // C = M*B;
Vector operator * (Vector& y); // z = M*y;
void size (int& m, int& n); // get size of matrix
};
a1,1 0 0 a1,4 0
0 a2,2 a2,3 0 a2,5
A=
0 a3,2 a3,3 0 0 .
a4,1 0 0 a4,4 a4,5
0 a5,2 0 a5,4 a5,5
class MatSparse
{
private:
double* A; // long vector with the nonzero matrix entries
int* irow; // indexing array
int* jcol; // indexing array
int m, n; // A is (logically) m times n
int nnz; // number of nonzeroes
public:
// the same functions as in the example above
// plus functionality for initializing the data structures
void prod (Vector& x, Vector& z); // M.prod(y,z); (z=M*y)
};
Object-oriented programming
Matrix = object
Details of storage schemes are hidden
Common interface to matrix operations
Base class: define operations, no data
Subclasses: implement specific storage schemes and
algorithms
It is possible to program with the base class only!
Bad news...
#include <CPUclock.h>
CPUclock clock;
clock.init();
// perform tasks ...
double cpu_time = clock.getCPUtime();
...
// perform more tasks
...
double cpu_time2 = clock.getCPUtime();
// perform even more tasks
...
double cpu_time3 = clock.getCPUtime();
class CPUclock
{
private:
clock_t t0;
public:
void init () { t0 = clock(); }
double getCPUtime() {
double t0_end = clock();
double cpu = double((t0_end - t0)/CLOCKS_PER_SEC)
t0 = clock_t(t0_end);
return cpu;
}
};
#ifndef CPUclock_H
#define CPUclock_H
#include <time.h> // clock function
#ifdef HAS_TMS
#include <sys/times.h> // tms struct
#endif
class CPUclock
{
private:
clock_t t0;
#ifdef HAS_TMS
tms t1, diff;
double cpu_time, child_cpu_time;
#endif
public:
void init ();
double getCPUtime();
};
#endif
CPUclock.cpp (1)
#include <CPUclock.h>
#ifdef HAS_TMS
#include <unistd.h>
#endif
void CPUclock:: init ()
{
t0 = clock();
#ifdef HAS_TMS
times(&t1);
#endif
}
CPUclock.cpp (2)
double CPUclock:: getCPUtime ()
{
double t0_end = clock();
double cpu_time_clock = double((t0_end - t0)/CLOCKS_PER_SEC);
#ifdef HAS_TMS
tms t2;
times(&t2);
diff.tms_utime = t2.tms_utime - t1.tms_utime;
diff.tms_stime = t2.tms_stime - t1.tms_stime;
diff.tms_cutime = t2.tms_cutime - t1.tms_cutime;
diff.tms_cstime = t2.tms_cstime - t1.tms_cstime;
double clock_ticks_per_sec = sysconf(_SC_CLK_TCK); // Linux
cpu_time_clock = double(diff.tms_utime + diff.tms_stime) \
/clock_ticks_per_sec;
child_cpu_time = \
double(diff.tms_cutime + diff.tms_cstime)/clock_ticks_per_sec;
// update t1 such that next getCPUtime() gives new difference:
times(&t1);
#endif
t0 = clock_t(t0_end);
return cpu_time_clock;
}
Extension
#include "Complex.h"
void main ()
{
Complex a(0,1); // imaginary unit
Complex b(2), c(3,-1);
Complex q = b;
std::cout << "q=" << q << ", a=" << a << ", b=" << b << "\n";
q = a*c + b/a;
std::cout << "Re(q)=" << q.Re() << ", Im(q)=" << q.Im() << "\n";
}
Extract the real and imaginary part (recall: these are private,
i.e., invisible for users of the class; here we get a copy of them
for reading)
double Complex:: Re () const { return re; }
double Complex:: Im () const { return im; }
Without const in
double Complex:: abs () { return sqrt(re*re + im*im); }
Overloaded operators
into
c = operator+ (a, b);
Consequence of inline
Consider
c = a + b;
that is,
c.operator= (operator+ (a,b));
Constructors
Writing
a = b
implies a call
a.operator= (b)
Copy constructor
The statements
Complex q = b;
Complex q(b);
Output function
First attempt:
inline Complex operator* (const Complex& a, const Complex& b)
{
Complex h; // Complex()
h.re = a.re*b.re - a.im*b.im;
h.im = a.im*b.re + a.re*b.im;
return h; // Complex(const Complex&)
}
Inline constructors
// e, c, d are complex
e = c*d;
// first compiler translation:
e.operator= (operator* (c,d));
// result of nested inline functions
// operator=, operator*, Complex(double,double=0):
e.re = c.re*d.re - c.im*d.im;
e.im = c.im*d.re + c.re*d.im;
Header file
Other files
Extract an entry:
double e = v(i);
or alternatively
a = inner(w,v);
The reason why these are declared outside the class, that the
functions take two arguments: the left and right operand
An alternative is to define the operators in the class, then the
left operand is the class (this object) and the argument is the
right operand
We recommend to define binary operators outside the class
with explicit left and right operand
Constructors (1)
Constructors (2)
More constructors:
MyVector v(n); // declare a vector of length n
// means calling the function
MyVector::MyVector (int n)
{ allocate(n); }
void MyVector::allocate (int n)
{
length = n;
A = new double[n]; // create n doubles in memory
}
Destructor
means calling
MyVector& MyVector::operator= (const MyVector& w)
// for setting v = w;
{
redim (w.size()); // make v as long as w
int i;
for (i = 0; i < length; i++) { // (C arrays start at 0)
A[i] = w.A[i];
}
return *this;
}
// return of *this, i.e. a MyVector&, allows nested
// assignments:
u = v = u_vec = v_vec;
Implementation:
bool MyVector::redim (int n)
{
if (length == n)
return false; // no need to allocate anything
else {
if (A != NULL) {
// "this" object has already allocated memory
deallocate();
}
allocate(n);
return true; // the length was changed
}
}
Inlined subscripting
The same function with a const keyword can only be used for
reading array values:
double c = a(2); // example
double operator() (int i) const
{ return A[i-1]; }
We can easily define standard C++ output syntax also for our
special MyVector objects:
// MyVector v
std::cout << v;
This is implemented as
std::ostream& operator<< (std::ostream& o, const MyVector& v)
{
v.print(o); return o;
}
Templates
Why?
returning Type means taking a copy of A[i-1], i.e., calling the
copy constructor, which is very inefficient if Type is a large
object (e.g. when we work with a vector of large grids)
Note
We have used int for length of arrays, but size t (an unsigned
integer type) is more standard in C/C++:
double* A;
size_t n; // length of A
u ax + y ,
Compile and link the F77 and C++ files (sometimes special
Fortran libraries like libF77.a must be linked)
where n is the number of entries and v1, v2, and so on are the
values of the vector entries
Compile, link and test the code
for reading the vector from some istream medium (test it with
a file and standard input)
Class MyArray
template <class T>
class MyArray
{
protected:
T* A; // vector entries (C-array)
int length;
public:
MyArray (); // MyArray<T> v;
MyArray (int n); // MyArray<T> v(n);
MyArray (const MyArray& w); // MyArray<T> v(w);
~MyArray (); // clean up dynamic memory
int redim (int n); // v.redim(m);
int size () const { return length; } // n = v.size();
MyArray& operator= (const MyArray& w); // v = w;
T operator() (int i) const; // a = v(i);
const T& operator() (int i); // v(i) = a;
T operator() (int i, int j) const; // a = v(p,q);
const T& operator() (int i, int j); // v(p,q) = a;
void print (ostream& o) const; // v.print(cout);
};
Memory-critical applications
class MemBoss
{
private:
char* chunk; // the memory segment to be managed
size_t size; // size of chunk in bytes
size_t used; // no of bytes used
std::list<char*> allocated_ptrs; // allocated segments
std::list<size_t> allocated_size; // size of each segment
public:
MemBoss(int chunksize)
{ size=chunksize; chunk = new char[size]; used=0; }
~MemBoss() { delete [] chunk; }
void* allocate(size_t nbytes)
{ char* p = chunk+used;
allocated_ptrs.insert_front(p);
allocated_size.insert_front(nbytes);
used += nbytes;
return (void*) p;
}
void deallocate(void* p); // more complicated
void printMemoryUsage(std::ostream& o);
};
Lessons learned
Container: vector
#include <vector>
std::vector<double> v(10, 3.2 /* default value */);
v[9] = 1001; // indexing, array starts at 0
const int n = v.size();
for (int j=0; j<n; j++)
std::cout << v[j] << " "; // only one index is possible
// vector of user-defined objects:
class MyClass { ... };
std::vector<MyClass> w(n);
Container: string
#include <string>
std::string s1 = "some string";
std::string s2;
s2 = s1 + " with more words";
std::string s3;
s3 = s2.substr(12 /*start index*/, 16 /*length*/);
printf("s1=%s, s3=%s\n", s1.c_str(), s3.c_str());
// std::strings c_str() returns a char* C string
STL lists
List:
#include <list>
std::list<std::string> slist;
slist.push_front("string 1"); // add at beginning
slist.push_front("string 2");
slist.push_back("string 3"); // add at end
slist.clear(); // erase the whole list
// slist<std::string>::iterator p; // list position
slist.erase(p); // erase element at p
slist.insert(p, "somestr"); // insert before p
Iterators (1)
Iterators (2)
Algorithms
Copy:
std::vector<T> v;
std::list<T> l;
...
// if l is at least as long as v:
std::copy(v.begin(), v.end(), l.begin());
// works when l is empty:
std::copy(v.begin(), v.end(), std::back_inserter(l));
Specializing algorithms
What is efficiency?
Human efficiency is most important for programmers
Computational efficiency is most important for program users
Premature optimization
Some rules
Avoid lists, sets etc, when arrays can be used without too
much waste of memory
Avoid calling small virtual functions in the innermost loop
(i.e., avoid object-oriented programming in the innermost
loop)
Implement a working code with emphasis on design for
extensions, maintenance, etc.
Analyze the efficiency with a tool (profiler) to predict the
CPU-intensive parts
Attack the CPU-intensive parts after the program is verified
Code:
MyVector somefunc(MyVector v) // copy!
{
MyVector r;
// compute with v and r
return r; // copy!
}
Hidden inefficiency
Why is this bad? What type of run-time failure can you think
of? (Hint: what happens in the destructor of w if you created
w by MyVector(u)?)
Efficiency tests
(368.0 s)
(366.0 s)
(505.0 s)
(485.0 s)
C
(58.0 s)
(58.0 s)
(242.0 s)
(242.0 s)
C++
Normalized CPU time
1
0.5
0
IBM HP SGI SUN
H. P. Langtangen Introduction to C++ (and C) Programming
Intro Classes Efficiency OOP
Test: DDOT
s (u, v )
(217.0 s)
(281.0 s)
(341.0 s)
(336.0 s)
1.2
(252.0 s)
(183.0 s)
(49.0 s)
1 C
C++
(42.0 s)
Normalized CPU time
0.8
0.6
0.4
0.2
0
IBM HP SGI SUN
H. P. Langtangen Introduction to C++ (and C) Programming
Intro Classes Efficiency OOP
Test: DGEMV
x Ay
1.5
(368.0 s)
(366.0 s)
(505.0 s)
(485.0 s)
C
(58.0 s)
(58.0 s)
(242.0 s)
(242.0 s)
C++
Normalized CPU time
1
0.5
0
IBM HP SGI SUN
H. P. Langtangen Introduction to C++ (and C) Programming
Intro Classes Efficiency OOP
Model:
u
+ ~v u = k2 u in 3D
t
Tests iterative solution (BiCGStab w/Jacobi prec.) of linear
systems
3 (630.0 s)
(492.0 s)
Grid size
(229.0 s)
2.5 100x20x10
200x20x10
Normalized CPU time
(940.0 s) 500x10x10
2 (1215.0 s)
(382.0 s)
(1534.0 s)
(2005.0 s)
1.5
(634.0 s)
0.5
0
IBM HP SGI
Model:
+ Ss S = K +1 in 1D
t t z z
(533.0 s)
1.8
(170.0 s)
Grid size
1.6
(177.0 s)
(59.0 s)
800
(20.0 s)
1.4 1,600
(114.0 s)
(38.0 s)
Normalized CPU time
3,200
(53.6 s)
1.2
(12.2 s)
0.8
0.6
0.4
0.2
0
IBM HP SGI
Test: convection-diffusion-reaction
Model:
convection-diffusion + u 2 in 1D
by Newtons method
Tests FE assembly
(4.7 s)
1.6
(165.0 s)
1.4 Grid size
(13.0 s)
(42.0 s)
1,000
1.2
10,000
Normalized CPU time
50,000
1
(576.0 s)
(149.0 s)
0.8
0.6
0.4
0.2
0
IBM SUN
Application example
2u
u u
= H(x, y ) + H(x, y )
t 2 x x y y
on a rectangular grid
Explicit 2nd-order finite difference scheme:
ui`+1 `1 ` ` ` ` `
,j = G (ui ,j , ui ,j , ui 1,j , ui +1,j , ui ,j1 , ui ,j+1 )
smart pointers
(automatic memory handling)
arrays
finite difference grid
scalar field over the grid
Array classes
plain C array
op()(int i)
VecSimplest
op()(int i, int j)
op< op<= etc VecSort ArrayGenSimplest op()(int i, int j, int k)
op+ op- op* op/ Vec ArrayGenSimple can print, scan, op=
Vector
PDE classes
Grid
Handle<X>
X* ptr
smart pointer
Field Grid
Handle<Grid> grid
Handle<ArrayGen> vec
ArrayGen& values() { return *vec; }
ArrayGen
Coding a scheme
class VecSimplest
{
class Vec : public virtual VecSimplest
protected:
{
double* a;
public:
int n;
Vec (int length);
public:
~Vec ();
VecSimplest (int length);
~VecSimplest ();
}
}
Vec object
Segment SA
Segment SB
double* a
virtual base
class pointer int n
Segment SC
#1 #2 #n
Lessons learned
User-given information
Improvements
Let all parts of the code work with ODE solvers through the
common base class interface:
void somefunc(ODESolver& solver, ...)
{
...
solver.advance(y,t,dt);
...
}
Here, solver will call the right algorithm, i.e., the advance
function in the subclass object that solver actually refers to
Result: All details of a specific ODE algorithm are hidden; we
just work with a generic ODE solver
User-provided functions
Generalizing
Define
class ODEProblem
{
// common data for all ODE problems
public:
virtual int size();
virtual void equation(MyArray<double>& f,
const MyArray<double>& y, double t);
virtual void scan();
};
ODE model:
y + c1 (y + c2 y |y |) + c3 (y + c4 y 3 ) = sin t
y1 = y2 f1
y2 = c1 (y2 + c2 y2 |y2 |) c3 (y1 + c4 y13 ) + sin t f2
From now on our program can work with a generic ODE solver and
a generic problem
ForwardEuler
RungeKutta4
Oscillator
RungeKutta4A
........
PDE problems
u
0.8
2.99
0.7 2.41
1.82
0.6
1.24
0.5
0.658
0.4 0.0753
0.507
0.3
1.09
0.2
1.67
0.1 2.26
2.84
0
0.4 0 0.8
PDE codes
2u
2 u 2 u
= c(x, y ) + c(x, y )
t 2 x x y y
0.07
0.7
0.056
0.042
0.028
0
0.014
8.67e19
0.014
0.028
1 0.042
0
0.056
10 20
10
0.07
20 0
Z
X
H. P. Langtangen Introduction to C++ (and C) Programming
Intro Classes Efficiency OOP ODEs PDEs
Basic abstractions
Flexible array
Grid
Scalar field
Time discretization parameters
Smart pointers
References:
Roeim and Langtangen: Implementation of a wave simulator
using objects and C++
Source code: src/C++/Wave2D
A grid class
Obvious ideas
collect grid information in a grid class
collect field information in a field class
Gain:
shorter code, closer to the mathematics
finite difference methods: minor
finite element methods: important
big programs: fundamental
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!)
Data representation:
...
// get total no of points in the grid:
int getNoPoints() const;
double Delta(int dimension) const;
double getPoint(int dimension, int index);
// start of indexed loops in dimension-direction:
int getBase(int dimension) const;
// end of indexed loops in dimension-direction:
int getMaxI(int dimension) const;
};
Mutators, i.e., functions for setting internal data members, are not
implemented here. Examples could be setDelta, setXmax, etc.
0.02
0.01
0
0 0.01 0.02 0.03 0.04 0.05 0.06
8
7
6
5
4
3
2
1
8.92 0 7.92
0.0535
0.0476
0.0416
0.0357
0.0297
0.0238
0.0178
0.0119
0.00595
0
0
0 1
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_;
}
Observations:
Pointers are bug no 1 in C/C++
Dynamic memory demands pointer programming
Lack of garbage collection (automatic clean-up of memory
that is no longer in use) means that manual deallocation is
required
Every new must be paried with a delete
Codes with memory leakage eat up the memory and slow
down computations
How to determine when memory is no longer in use? Suppose
5 fields point to the same grid, when can we safely remove the
grid object?
Advantages:
negligible overhead
(kind of) automatic garbage collection
several fields can safely share one grid
Simulator classes
GridLattice
FieldLattice for the unknown u field at three consecutive time
levels
TimePrm
Class hierarchy of functions:
1 initial surface functions I(x,y) and/or
2 bottom functions H(x,y)
Hierarchy of functions
Example
Example cont.
inline real GaussianBell:: valuePt(real x, real y, real)
{
real r = A*exp(-(sqr(x - xc)/(2*sqr(sigma_x))
+ sqr(y - yc)/(2*sqr(sigma_y))));
return r;
}
GaussianBell:: GaussianBell(char fname_)
{ fname = fname_; }
std::string& GaussianBell:: formula()
{ return formula_str; }
void GaussianBell:: scan ()
{
A = CommandLineArgs::read("-A_" + fname, 0.1);
sigma_x = CommandLineArgs::read("-sigma_x_" + fname, 0.5);
sigma_y = CommandLineArgs::read("-sigma_y_" + fname, 0.5);
xc = CommandLineArgs::read("-xc_" + fname, 0.0);
yc = CommandLineArgs::read("-yc_" + fname, 0.0);
}
class Wave2D
{
Handle<GridLattice> grid;
Handle<FieldLattice> up; // solution at time level l+1
Handle<FieldLattice> u; // solution at time level l
Handle<FieldLattice> um; // solution at time level l-1
Handle<TimePrm> tip;
Handle<WaveFunc> I; // initial surface
Handle<WaveFunc> H; // bottom function
// load H into a field lambda for efficiency:
Handle<FieldLattice> lambda;
void timeLoop(); // perform time stepping
void plot(bool initial); // dump fields to file, plot later
void WAVE(FieldLattice& up, const FieldLattice& u,
const FieldLattice& um, real a, real b, real c);
void setIC(); // set initial conditions
real calculateDt(int func); // calculate optimal timestep
public:
void scan(); // read input and initialize
void solveProblem(); // start the simulation
};
2u
u u
H(x, y ) + H(x, y ) = , in
x x y y t 2
u
= 0, on
n
u(x, y , 0) = I (x, y ), in
u(x, y , 0) = 0, in
t
Discretization (1)
b b b b b b b b
b b b (i,j+1)
b b b b b
b b b(i-1,j) b(i,j) b(i+1,j)b b b
b b b b(i,j-1) b b b b
b b b b b b b b
Discretization (2)
2u ui`+1 ` `1
,j 2ui ,j + ui ,j
t 2 t 2
Similarly for the x and y derivatives.
Assume for the moment that H 1, then
ui`+1 ` `1
,j 2ui ,j + ui ,j ui`+1,j 2ui`,j + ui`1,j
= +
t 2 x 2
ui ,j+1 2ui`,j + ui`,j1
`
y 2
Discretization (3)
Graphical illustration
`+1
aui,j
`
aui,j1 `
@
aui+1,j
@ a u`
@i,j
a
@aui,j+1
`
ui1,j `
`1
aui,j
a1
ar 2 ar 2
@
@ a 2 4r 2
@
a
r2 @ar 2
a1
Discretization (4)
Thus we derive
ui`+1
,j = 2ui`,j ui`1
,j
+rx2 H 1 ui`+1,j ui`,j H 1 ui`,j ui`1,j
i + 2 ,j i 2 ,j
+ry2 H 1 ui`,j+1 ui`,j H 1 ui`,j ui`,j1
i ,j+ 2 i ,j 2
= 2ui`,j ui`1
,j + [u]`i ,j
Algorithm (1)
Define:
storage ui+,j , ui ,j , ui,j for ui`+1 ` `1
,j , ui ,j , ui ,j
whole grid: () = {i = 1, . . . , nx , j = 1, . . . , ny }
inner points: () = {i = 2, . . . , nx 1, j = 1, . . . , ny 1}
Set initial conditions
ui ,j = I (xi , yj ), (i, j) ()
Define ui,j
Algorithm (2)
Set t = 0
While t < tstop
t = t + t
Update all inner points
ui,j = ui ,j , ui ,j = ui+,j ,
(i, j) ()
(without H)
u u
0= = u (0, 1) =
n y
1
1 0 1 2 3 4 5 6 7 8 9 10
9 9
8 8
7 7
6 6
5 5
4 4
3 3
2 2
1 1
0 0
1 1
1 0 1 2 3 4 5 6 7 8 9 10 1 0 1 2 3 4 5 6 7 8 9 10
WAVE(u + , u, u , a, b, c)
Modified algorithm
Definitions: as above
Initial conditions: ui ,j = I (xi , yj ),
(i, j) ()
Variable coefficient: set/get values for
Set artificial quantity ui,j : WAVE(u , u, u , 0.5, 0, 0.5)
Set t = 0
While t tstop
t t + t
(If depends on t: update )
update all points: WAVE(u + , u, u , 1, 1, 1)
initialize for next step:
ui,j = ui ,j , ui ,j = ui+,j , (i, j) ()
0.1 0.1
0.08 0.08
0.06 0.06
0.04 0.04
0.02 0.02
0 0
0.02 0.02
0.04 0.04
50 50
40 50 40 50
30 40 30 40
20 30 20 30
20 20
10 10
10 10
0 0 0 0
0.1 0.1
0.08 0.08
0.06 0.06
0.04 0.04
0.02 0.02
0 0
0.02 0.02
0.04 0.04
50 50
40 50 40 50
30 40 30 40
20 30 20 30
20 20
10 10
10 10
0 0 0 0
2u
= H(x)u
t 2
Rectangular domain = (sx , sx + wx ) (sy , sy + wy ) with
initial (Gaussian bell) function
!
1 x xuc 2 1 y yuc 2
I (x, y ) = Au exp
2 ux 2 uy
Acknowledgements
Igor Rafienko
Vetle Roeim
Knut-Andreas Lie