#pragma once #include #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 xmin, const double xmax, const double ymin, const double ymax, const double h, const double k, std::function fun, std::function gN, std::function gE, std::function gS, std::function gW ) : _iter{0}, _nx{nx}, _ny{ny}, _xmin{xmin}, _xmax{xmax}, _ymin{ymin}, _ymax{ymax}, _stencil{ 4.0 / (h * h) + 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, 0.), _sol(nx, ny, 0.), _tmp(nx, ny, 0.) { // Initialize Right Hand Size _rhs(x, y) = f(X(x), Y(y)) 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.get(x, -1) = _tmp.get(x, -1) = gN(X(x)); } // East for (size_t y = 0; y < ny; ++y) { _sol.get(-1, y) = _tmp.get(-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 (i, j) indices to (x, y) position in the [xmin, xmax] x [ymin, ymax] domain */ double X(size_t i) const { double ratio = static_cast(i) / static_cast(_nx - 1); return _xmin + ratio * (_xmax - _xmin); } double Y(size_t j) const { double ratio = static_cast(j) / static_cast(_ny - 1); return _ymin + ratio * (_ymax - _ymin); } enum class Dir { North, East, South, West, N = North, E = East, S = South, W = West }; MatrixView read_boundary(enum Dir dir) { switch (dir) { case Dir::North: return Col(_sol, -2); case Dir::East: return Row(_sol, -2); case Dir::South: return Col(_sol, 1); default: // Dir::West return Row(_sol, 1); } }; MatrixView write_boundary(enum Dir dir) { switch (dir) { case Dir::North: return Col(_sol, -1); case Dir::East: return Row(_sol, -1); case Dir::South: return Col(_sol, 0); default: // Dir::West return Row(_sol, 0); } }; /** Solution getter */ Matrix& solution() { return _sol; } /** Right Hand Side getter (grid evaluated f(x, y)) */ Matrix& rhs() { return _rhs; } /** Performs a single Jacobi 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++; }; /** Computes the L2 and Inf norm of the residuals */ template double resid_norm() { // Enforce valid Norm types static_assert(N == Norm::Frob || N == Norm::Max); // Norm accumulator double accum = 0.0; for (size_t y = 1; y < _ny - 1; ++y) { for (size_t x = 1; x < _nx - 1; ++x) { double resid = _rhs(x, y) - (_stencil.C * _sol(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)); if constexpr (N == Norm::Frob) { accum += resid * resid; } else { // Maximum Norm accum = std::max(std::abs(resid), accum); } } } if constexpr (N == Norm::Frob) { return std::sqrt(accum); } else { // Maximum Norm return accum; } } 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 double _xmin; /*< Domain X-min (west border position) */ const double _xmax; /*< Domain X-max (east border position) */ const double _ymin; /*< Domain Y-min (south border position) */ const double _ymax; /*< Domain Y-max (north border position) */ const Stencil _stencil; /*< Simple '+' shaped stencil */ Matrix _rhs; /*< Grid evaluated RHS of the PDE, f(x, y) */ Matrix _sol; /*< Solution after `_iter` iterations */ Matrix _tmp; /*< Temp. data block, used in iterate() */ };