wip: Exercise 01

This commit is contained in:
Daniel Kapla 2022-03-11 18:52:38 +01:00
parent 3bf63e12ba
commit 68da82c836
5 changed files with 408 additions and 37 deletions

20
Exercise_01/Makefile Normal file
View File

@ -0,0 +1,20 @@
CPP=g++
MPICPP=mpic++
CPPFLAGS= -std=c++17 -O3 -Wall -Wpedantic -pedantic
OUT=main
seriel: main.cpp Matrix.h Solver.h
$(CPP) main.cpp $(CPPFLAGS) -o $(OUT)_seriel
mpi: main.cpp Matrix.h Solver.h
$(MPICPP) main.cpp $(CPPFLAGS) -DUSE_MPI -o $(OUT)_mpi
all: seriel mpi
test: seriel mpi
./$(OUT)_seriel 120 10
mpirun -n 4 ./$(OUT)_mpi 120 10
clean:
rm -f *.o main $(OUT)_seriel $(OUT)_mpi

View File

@ -3,49 +3,132 @@
#include <stddef.h> #include <stddef.h>
#include <ostream> #include <ostream>
#include <vector> #include <vector>
#define _USE_MATH_DEFINES /* enables math constants from cmath */
#include <cmath>
template <typename T> inline size_t mod(int num, size_t module) {
class MatrixView; while (num < 0) { num += module; };
return static_cast<size_t>(num % module);
}
enum Norm { Frob, Max };
template <typename T> template <typename T>
class Matrix { class Matrix {
public: public:
Matrix(size_t nrow, size_t ncol) : _nrow{nrow}, _ncol{ncol} { Matrix(size_t nrow, size_t ncol) : _nrow{nrow}, _ncol{ncol} {
_elem.reserve(nrow * ncol); _data.reserve(nrow * ncol);
};
Matrix(size_t nrow, size_t ncol, T elem) : Matrix(nrow, ncol) {
for (size_t i = 0; i < nrow * ncol; ++i) {
_data[i] = elem;
}
}; };
size_t nrow() const { return _nrow; }; size_t nrow() const { return _nrow; };
size_t ncol() const { return _ncol; }; size_t ncol() const { return _ncol; };
size_t nx() const { return _ncol; };
size_t ny() const { return _nrow; };
size_t size() const { return _nrow * _ncol; }; size_t size() const { return _nrow * _ncol; };
T& operator()(int i) { return _elem[index(i)]; }; T* data() { return _data.data(); };
const T& operator()(int i) const { return _elem[index(i)]; }; const T* data() const { return _data.data(); };
T& operator()(int i, int j) { return _elem[index(i, j)]; };
const T& operator()(int i, int j) const { return _elem[index(i, j)]; }; T& operator()(int i) { return _data[index(i)]; };
const T& operator()(int i) const { return _data[index(i)]; };
T& operator()(int i, int j) { return _data[index(i, j)]; }
const T& operator()(int i, int j) const { return _data[index(i, j)]; }
template <enum Norm N = Norm::Frob>
double norm() const {
T accum = static_cast<T>(0); /*< result accumulator */
// Enforce valid norm types (at compile time)
static_assert(N == Norm::Frob || N == Norm::Max);
if constexpr (N == Norm::Frob) {
// Sum of Squares
for (const T& elem : _data) {
accum += elem * elem;
}
// The Frobenius norm is the square root of the sum of squares
return std::sqrt(static_cast<double>(accum));
} else { // Maximum Norm
// Maximum absolute value
for (const T& elem : _data) {
if (accum < std::abs(elem)) {
accum = std::abs(elem);
}
}
return static_cast<double>(accum);
}
}
/**
* Distance of this Matrix to given matrix A as || this - A ||_N
*
* Note: It there is a dimension missmatch, only the number of elements
* of the smaller matrix are considured.
*/
template <enum Norm N = Norm::Frob>
double dist(const Matrix<T>& A) const {
T accum = static_cast<T>(0); /*< result accomulator */
size_t nelem = size() < A.size() ? size() : A.size();
// Enforce valid norm types (at compile time)
static_assert(N == Norm::Frob || N == Norm::Max);
if constexpr (N == Norm::Frob) {
// Sum of Squared differences
for (size_t i = 0; i < nelem; ++i) {
T diff = this(i) - A(i);
accum += diff * diff;
}
return std::sqrt(static_cast<double>(accum));
} else { // Maximum Norm
for (size_t i = 0; i < nelem; ++i) {
T diff = std::abs(this(i) - A(i));
if (accum < diff) {
accum = diff;
}
}
return static_cast<double>(accum);
}
}
/** overloaded standard swap for matrices (of same type)
*
* @example
* Matrix<int> A(1, 2);
* Matrix<int> B(3, 4);
* std::swap(A, B);
*/
friend void swap(Matrix<T>& A, Matrix<T>& B) {
std::swap(A._nrow, B._nrow);
std::swap(A._ncol, B._ncol);
std::swap(A._data, B._data);
}
private: private:
size_t _nrow; size_t _nrow; /*< Number of Rows */
size_t _ncol; size_t _ncol; /*< Number of Columns */
std::vector<T> _elem; std::vector<T> _data; /*< Matrix elements / Data */
size_t index(int i) const { size_t index(int i) const {
int nelem = static_cast<int>(_nrow * _ncol); const int nelem = static_cast<int>(_nrow * _ncol);
while (i < 0) { i += nelem; } while (i < 0) { i += nelem; }
while (i >= nelem) { i -= nelem; } while (i >= nelem) { i -= nelem; }
return i; return i;
}; };
size_t index(int i, int j) const { size_t index(int i, int j) const {
int nrow = static_cast<int>(_nrow); return static_cast<size_t>(mod(i, _nrow) + mod(j, _ncol) * _nrow);
int ncol = static_cast<int>(_ncol); }
while (i < 0) { i += nrow; }
while (i >= nrow) { i -= nrow; }
while (j < 0) { j += ncol; }
while (j >= ncol) { j -= ncol; }
return static_cast<size_t>(i + j * _nrow);
};
}; };
template <typename T> template <typename T>
@ -68,6 +151,11 @@ public:
const size_t size() const { return _nelem; }; const size_t size() const { return _nelem; };
const size_t stride() const { return _stride; };
T* data() { return _matrix.data() + _index; };
const T* data() const { return _matrix.data() + _index; };
T& operator()(int i) { return _matrix(_index + i * _stride); }; T& operator()(int i) { return _matrix(_index + i * _stride); };
const T& operator()(int i) const { return _matrix(_index + i * _stride); }; const T& operator()(int i) const { return _matrix(_index + i * _stride); };
@ -90,15 +178,17 @@ std::ostream& operator<<(std::ostream& out, const MatrixView<T>& view) {
template <typename T> template <typename T>
class Row : public MatrixView<T> { class Row : public MatrixView<T> {
public: public:
Row(Matrix<T>& matrix, size_t index) : Row(Matrix<T>& matrix, int index) :
MatrixView<T>(matrix, index * matrix.ncol(), 1, matrix.nrow()) { }; MatrixView<T>(matrix, mod(index, matrix.nrow()),
matrix.nrow(), matrix.ncol()) { };
}; };
template <typename T> template <typename T>
class Col : public MatrixView<T> { class Col : public MatrixView<T> {
public: public:
Col(Matrix<T>& matrix, size_t index) : Col(Matrix<T>& matrix, int index) :
MatrixView<T>(matrix, index, matrix.nrow(), matrix.ncol()) { }; MatrixView<T>(matrix, mod(index, matrix.ncol()) * matrix.nrow(),
1, matrix.nrow()) { };
}; };
template <typename T> template <typename T>

114
Exercise_01/Solver.h Normal file
View File

@ -0,0 +1,114 @@
#pragma once
#include <functional>
#include "Matrix.h"
/** Stencil
*/
struct Stencil {
const double C; /*< Center */
const double B; /*< Bottom */
const double L; /*< Left */
const double U; /*< Up */
const double R; /*< Right */
};
class Solver {
public:
Solver(const size_t nx, const size_t ny, const double h, const double k,
std::function<double(double, double)> fun,
std::function<double(double)> gN,
std::function<double(double)> gE,
std::function<double(double)> gS,
std::function<double(double)> gW
) :
_iter{0},
_nx{nx},
_ny{ny},
_stencil{ 4.0 / (h * h) + 4.0 * k * k, /* Center */
-1.0 / (h * h), /* Bottom */
-1.0 / (h * h), /* Left */
-1.0 / (h * h), /* Up */
-1.0 / (h * h)}, /* Right */
_rhs(nx, ny),
_sol(nx, ny, 0.0),
_tmp(nx, ny)
{
// Initialize Right Hand Size _rhs(x, y) = f(X(x), Y(y))
// Note that the correspondence usual matrix indexing sceeme as
// row/colums indices lead to an missplaces representation if the matrix
// is printed in the usual format (x <-> rows, y <-> negative columns).
// If this in entierly ignored and only considured in the case of
// printing the matrix, everything is fine.
for (size_t x = 0; x < nx; ++x) {
for (size_t y = 0; y < ny; ++y) {
_rhs(x, y) = fun(X(x), Y(y));
}
}
// Set Dirichlet boundary conditions
// North
for (size_t x = 0; x < nx; ++x) { _sol(x, -1) = _tmp(x, -1) = gN(X(x)); }
// East
for (size_t y = 0; y < ny; ++y) { _sol(-1, y) = _tmp(-1, y) = gE(Y(y)); }
// South
for (size_t x = 0; x < nx; ++x) { _sol(x, 0) = _tmp(x, 0) = gS(X(x)); }
// West
for (size_t y = 0; y < ny; ++y) { _sol(0, y) = _tmp(0, y) = gW(Y(y)); }
};
/* Maps x/y index to x/y position in the [0, 1] x [0, 1] domain */
double X(size_t x) const {
return static_cast<double>(x) / static_cast<double>(_nx - 1);
}
double Y(size_t y) const {
return static_cast<double>(y) / static_cast<double>(_ny - 1);
}
enum class Dir {
North, East, South, West,
N = North, E = East, S = South, W = West
};
MatrixView<double> boundary(enum Dir dir) {
switch (dir) {
case Dir::North:
return Col<double>(_sol, -1);
case Dir::East:
return Row<double>(_sol, -1);
case Dir::South:
return Col<double>(_sol, 0);
default: // Dir::West
return Row<double>(_sol, 0);
}
};
/**
* Performs a single Jacobian Iteration
*/
void iterate() {
double s = 1.0 / _stencil.C;
for (size_t y = 1; y < _ny - 1; ++y) {
for (size_t x = 1; x < _nx - 1; ++x) {
_tmp(x, y) = s * (_rhs(x, y) - (_stencil.B * _sol(x, y - 1) +
_stencil.L * _sol(x - 1, y) +
_stencil.U * _sol(x, y + 1) +
_stencil.R * _sol(x + 1, y)));
}
}
std::swap(_tmp, _sol);
_iter++;
};
private:
size_t _iter; /*< Iteration count */
const size_t _nx; /*< Number of X-axis grid points */
const size_t _ny; /*< Number of Y-axis grid points */
const Stencil _stencil; /*< Simple, + shaped stencil */
Matrix<double> _rhs; /*< Grid evaluated RHS of the PDE = f(x, y) */
Matrix<double> _sol; /*< Solution after _iter iterations */
Matrix<double> _tmp; /*< Temp. datablock, used in iterate() */
};

View File

@ -0,0 +1,78 @@
/**
* Compile
* mpic++ MPI_Send_Recv.cpp -Wall -pedantic -Wpedantic -o MPI_Send_Recv
* Usage
* mpirun -n 4 ./MPI_Send_Recv
*
* Note: An easy way to finde the location of the `mpi.h` file is
* mpic++ --showme:compile
* which might be usefull to configure the intellicense.
*/
#include <iostream>
#include <sstream>
#include <mpi.h>
/** min of two ints (fine for an example) */
int min(int a, int b) { return a < b ? a : b; }
int main(int argn, char* argv[]) {
// Initialize MPI (always required)
MPI_Init(nullptr, nullptr);
// Allocate MPI Settings
int mpi_size; /*< Number of processes */
int mpi_rank; /*< This process rank (a.k.a. the MPI process ID) */
// Set/Get MPI Settings
MPI_Comm_size(MPI_COMM_WORLD, &mpi_size);
MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank);
// Write MPI info to stdout
std::cout << "Hello World, i am rank " << mpi_rank << " of " << mpi_size
<< " processes." << std::endl;
// setting this to small truncates the message (Try it, works fine.)
constexpr int max_size = 128; /*< Maximum message length */
// Distinguish between rank 0 and the rest
if (mpi_rank == 0) {
// Send a different message to all other ranks
for (int rank = 1; rank < mpi_size; ++rank) {
// Build message
std::ostringstream message;
message << "Hello rank nr. " << rank;
std::string sendbuf = message.str();
// Send message
MPI_Send(sendbuf.c_str(), min(max_size, sendbuf.size()),
MPI_CHAR, rank, sendbuf.size(), MPI_COMM_WORLD);
// Note: the tag beeing set to the string length (hacky, but :-})
}
// Report your done
std::cout << "Done: rank 0 send all messages." << std::endl;
} else {
char recvbuf[max_size]; /*< MPI_Recv buffer, e.g. memory to write to */
MPI_Status status; /*< MPI Status object, e.g. communication info */
// Each rank listens for a single message from rank 0
MPI_Recv(recvbuf, max_size,
MPI_CHAR, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
if (status.MPI_ERROR) {
std::cerr << "Error: Recv reported an Error?!" << std::endl;
} else {
// Ensure C-style string is terminated and of propper size as
// the tag encodes the string length.
// Should be null terminated, but better save than sorry.
recvbuf[min(max_size - 1, status.MPI_TAG)] = '\0';
// Print receved message to console
std::cout << "Recv: " << recvbuf << std::endl;
}
}
// Shutdown MPI (always required)
MPI_Finalize();
return 0;
}

View File

@ -2,29 +2,98 @@
* g++ main.cpp -std=c++17 -Wall -Wpedantic -pedantic -o main; ./main * g++ main.cpp -std=c++17 -Wall -Wpedantic -pedantic -o main; ./main
*/ */
#include <stddef.h>
#include <iostream> #include <iostream>
#include <functional>
#define _USE_MATH_DEFINES /* enables math constants from cmath */
#include <cmath>
#ifdef USE_MPI
#include <mpi.h>
#endif
#include "Matrix.h" #include "Matrix.h"
#include "Solver.h"
int main(int argn, char* argv[]) { int main(int argn, char* argv[]) {
Matrix<int> mat(3, 4); /******************************* MPI Setup ********************************/
for (size_t i = 0; i < mat.size(); ++i) { #ifdef USE_MPI
mat(i) = i; // Initialize MPI
MPI_Init(nullptr, nullptr);
// Get MPI config
int mpi_size; /*< MPI pool size (a.k.a. total number of processes) */
int mpi_rank; /*< MPI rank (a.k.a. process ID in the context of MPI) */
MPI_Comm_size(MPI_COMM_WORLD, &mpi_size);
MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank);
#endif
/**************************** Parse Arguments *****************************/
if (argn < 3) {
std::cerr << "usage: " << argv[0] << " <resolution> <iterations>" << std::endl;
return -1;
} else if (argn > 3) {
std::cerr << "warning: " << "ignoring all but the first two params" << std::endl;
}
// TODO: make this proper!!!
size_t resolution = atol(argv[1]);
size_t iterations = atol(argv[2]);
if (resolution < 1 || resolution > 65536
|| iterations < 1 || iterations > 65536) {
std::cerr << "error: parsing arguments failed" << std::endl;
} }
// std::cout << "Matrix:\n" << mat(2, 1) << std::endl;
std::cout << "Matrix:\n" << mat.nrow() << std::endl;
std::cout << "Matrix:\n" << mat << std::endl; /************************* Initialize PDE Solver **************************/
size_t nx = resolution;
size_t ny = resolution;
const double k = M_PI;
const double h = 1.0 / static_cast<double>(resolution - 1);
Col<int> colView(mat, 2); // Declare right hand side function f(x, y) = k^2 sin(2 pi x) sinh(2 pi y)
Row<int> rowView(mat, 1); std::function<double(double, double)> fun = [k](double x, double y) {
Diag<int> diagView(mat); return k * k * sin(M_2_PI * x) * sinh(M_2_PI * y);
};
// Boundary conditions
/** North boundary condition g(x) = k^2 sin(2 pi x) sinh(2 pi) */
std::function<double(double)> gN = [k](double x) {
return k * k * sin(M_2_PI * x) * sinh(M_2_PI);
};
/** East, South and West boundary conditions are simply = 0 */
std::function<double(double)> g0 = [k](double) { return 0.0; };
std::cout << "col:\n" << colView << std::endl; // Instanciate solver
std::cout << "row:\n" << rowView << std::endl; Solver solver(nx, ny, h, k, fun, gN, g0, g0, g0);
std::cout << "diag:\n" << diagView << std::endl;
// Set Diriclet boundary conditions (East, South and West to 0)
for (auto dir : { Solver::Dir::E, Solver::Dir::S, Solver::Dir::W }) {
MatrixView<double> boundary = solver.boundary(dir);
for (size_t i = 0; i < boundary.size(); ++i) {
boundary(i) = 0.0;
}
}
// The North boundary condition is g(x) = sin(2 pi x) sinh(2 pi)
{
MatrixView<double> boundary = solver.boundary(Solver::Dir::North);
for (size_t i = 0; i < boundary.size(); ++i) {
double x = static_cast<double>(i) / static_cast<double>(nx - 1);
boundary(i) = sin(M_2_PI * x) * sinh(M_2_PI);
}
}
/******************************* Solve PDE ********************************/
// Run solver iterations
for (size_t iter = 0; iter < iterations; ++iter) {
solver.iterate();
}
/****************************** Tests/Report ******************************/
// MPI shutdown/cleanup
#ifdef USE_MPI
MPI_Finalize();
#endif
return 0; return 0;
} }