(Simple) Multigrid solver
Content |
This example a multigrid solver that uses the grids \(\Omega_{h}\), \(\Omega_{2h}\), \(\Omega_{4h}\), \(\Omega_{8h}\)
One iteration a multigrid V-cycle
Code
#include <cmath> #include <hpc/matvec/axpy.hpp> #include <hpc/matvec/copy.hpp> #include <hpc/matvec/densevector.hpp> #include <hpc/matvec/iterators.hpp> #include <hpc/matvec/traits.hpp> #include <hpc/matvec/print.hpp> //------------------------------------------------------------------------------ using namespace hpc; using namespace hpc::matvec; template <typename T> const T & assertEqual(const T &a, const T &b) { assert(a==b); return a; } //------------------------------------------------------------------------------ template < typename F, typename G, typename T, template<typename> class VectorF, template<typename> class VectorU, Require< Dense<VectorF<T>>, Dense<VectorU<T>> > = true > void discretizeProblem(const F &f, const G &g, VectorF<T> &f_h, VectorU<T> &u_h) { auto N = assertEqual(f_h.length(), u_h.length()) - 2; auto h = T(1)/(N+1); u_h(0) = g(0); u_h(N+1) = g(1); for (std::size_t i=1; i<N; ++i) { u_h(i) = 0; f_h(i) = f(i*h); } // actually we don't have to initialize f_h(0), f_h(N+1) so we store some // good and evil numbers there: f_h(0) = 42; f_h(N+1) = 666; } //------------------------------------------------------------------------------ template < typename T, template<typename> class Vector, Require< Dense<Vector<T>> > = true > T lInfNorm(const Vector<T> &x_h) { T res = 0; for (auto [i, xi] : x_h) { i=i; if (std::abs(xi) > res) { res = std::abs(xi); } } return res; } //------------------------------------------------------------------------------ template < typename U, typename T, template<typename> class VectorU, template<typename> class VectorE, Require< Dense<VectorU<T>>, Dense<VectorE<T>> > = true > void evalError(const U &u, const VectorU<T> &u_h, VectorE<T> &e_h) { auto N = assertEqual(u_h.length(), e_h.length()) - 2; auto h = T(1)/(N+1); for (auto [i, ei] : e_h) { ei = u(i*h) - u_h(i); } } //------------------------------------------------------------------------------ template < typename T, template<typename> class VectorU, template<typename> class VectorF, template<typename> class VectorR, Require< Dense<VectorF<T>>, Dense<VectorU<T>>, Dense<VectorR<T>> > = true > void evalResidual(const VectorU<T> &u_h, const VectorF<T> &f_h, VectorR<T> &r_h) { auto N = assertEqual(u_h.length(), r_h.length()) - 2; auto h = T(1)/(N+1); auto h2 = h*h; r_h(0) = r_h(N+1) = 0; for (std::size_t i=1; i<=N; ++i) { r_h(i) = f_h(i) - 1./h2 * (2*u_h(i) - u_h(i-1) - u_h(i+1)); } } //------------------------------------------------------------------------------ template < typename T, template<typename> class VectorF, template<typename> class VectorU, Require< Dense<VectorF<T>>, Dense<VectorU<T>> > = true > void direct_solver(const VectorF<T> &f_h, VectorU<T> &u_h) { auto N = assertEqual(f_h.length(), u_h.length()) - 2; auto h = T(1)/(N+1); auto h2 = h*h; // forward substitution u_h(1) = h2*f_h(1) + u_h(0); for (std::size_t k=2; k<N; ++k) { u_h(k) = h2*f_h(k) + u_h(k-1)*(k-1)/k; } u_h(N) = h2*f_h(N) + u_h(N+1) + u_h(N-1)*(N-1)/N; // backward substitution u_h(N) = u_h(N)*N/(N+1); for (std::size_t k=N-1; k>=1; --k) { u_h(k) = (u_h(k)+u_h(k+1))*k/(k+1); } } //============================================================================== template < typename T, template<typename> class Vector, Require< Dense<Vector<T>> > = true > void addNoise(std::size_t k, Vector<T> &x_h) { auto N = x_h.length()-2; auto h = T(1)/(N+1); for (std::size_t i=1; i<=N; ++i) { x_h(i) += sin(M_PI*k*i*h); } } //------------------------------------------------------------------------------ template < typename T, template<typename> class VectorF, template<typename> class VectorU, template<typename> class VectorS, Require< Dense<VectorF<T>>, Dense<VectorU<T>>, Dense<VectorS<T>> > = true > void jacobi_step(double omega, const VectorF<T> &f_h, const VectorU<T> &u_h, VectorS<T> &s_h) { auto N = assertEqual(f_h.length(), u_h.length()) - 2; auto h = T(1)/(N+1); auto h2 = h*h; // TODO: Your code s_h(0) = u_h(0); s_h(N+1) = u_h(N+1); } //------------------------------------------------------------------------------ int main() { /* auto f = [](double x) { return -2; }; auto g = [](double x) { return 1 + 3*x; }; auto u = [](double x) { return (x+1)*(x+1); }; */ auto f = [](double x) { return 0; }; auto g = [](double x) { return 0; }; auto u = [](double x) { return 0; }; std::size_t N = 100; DenseVector<double> f_h(N+2), u_h(N+2), tmp_h(N+2), r_h(N+2), e_h(N+2); discretizeProblem(f, g, f_h, u_h); // First initialize u_h with exact solution direct_solver(f_h, u_h); // then add some noise addNoise(1, u_h); //addNoise(N/2, u_h); //addNoise(N-3, u_h); addNoise(N-1, u_h); double omega = 0.5; // smoothing parameter for (std::size_t it=0; it<=20; ++it) { jacobi_step(omega, f_h, u_h, tmp_h); jacobi_step(omega, f_h, tmp_h, u_h); evalResidual(u_h, f_h, r_h); evalError(u, u_h, e_h); fmt::printf("#lInfNorm(r_h) = %.5e\n", lInfNorm(r_h)); fmt::printf("#lInfNorm(e_h) = %.5e\n", lInfNorm(e_h)); fmt::printf("it %3d\n", it); for (auto [i,ri] : r_h) { fmt::printf("%d %f\n", i, ri); } fmt::printf("\n\n"); } }
Code for gnuplot
set terminal svg size 900, 500 set output "restrict_post_smooth.svg" set xlabel "Grid points" set ylabel "Residual value" set title "Smoothing" set key outside set key autotitle columnhead set pointsize 0.5 plot "restrict_post_smooth.data" index 0 using 1:2 with linespoints lt 1 lw 1, \ "restrict_post_smooth.data" index 1 using 1:2 with linespoints lt 2 lw 3
How to compile and plot results
theon$ g++ -Wall -I /home/numerik/pub/hpc/ws18/session25/ -std=c++17 -o restrict_post_smooth restrict_post_smooth.cpp theon$ ./restrict_post_smooth > restrict_post_smooth.data theon$
Plotting results
theon$ gnuplot restrict_post_smooth.gnuplot theon$ gnuplot restrict_post_smooth2.gnuplot theon$ gnuplot restrict_post_smooth3.gnuplot theon$ gnuplot restrict_post_smooth4.gnuplot theon$ gnuplot restrict_post_smooth5.gnuplot theon$ gnuplot restrict_post_smooth6.gnuplot theon$ gnuplot restrict_post_smooth7.gnuplot theon$ gnuplot restrict_post_smooth8.gnuplot theon$
Plot of smoothing properties
Restriction on \(\Omega_{2h}\) and post smoothing
Restriction on \(\Omega_{4h}\) and post smoothing
Restriction on \(\Omega_{8h}\) and post smoothing
Multigrid with 4 levels
Code
#include <cmath> #include <sstream> #include <memory> #include <vector> #include <hpc/matvec/axpy.hpp> #include <hpc/matvec/copy.hpp> #include <hpc/matvec/densevector.hpp> #include <hpc/matvec/iterators.hpp> #include <hpc/matvec/traits.hpp> #include <hpc/matvec/print.hpp> //------------------------------------------------------------------------------ using namespace hpc; using namespace hpc::matvec; template <typename T> const T & assertEqual(const T &a, const T &b) { assert(a==b); return a; } //------------------------------------------------------------------------------ template < typename F, typename G, typename T, template<typename> class VectorF, template<typename> class VectorU, Require< Dense<VectorF<T>>, Dense<VectorU<T>> > = true > void discretizeProblem(const F &f, const G &g, VectorF<T> &f_h, VectorU<T> &u_h) { auto N = assertEqual(f_h.length(), u_h.length()) - 2; auto h = T(1)/(N+1); u_h(0) = g(0); u_h(N+1) = g(1); for (auto [i, fi] : f_h) { fi = f(i*h); } } //------------------------------------------------------------------------------ template < typename U, typename T, template<typename> class VectorU, template<typename> class VectorE, Require< Dense<VectorU<T>>, Dense<VectorE<T>> > = true > void evalError(const U &u, const VectorU<T> &u_h, VectorE<T> &e_h) { auto N = assertEqual(u_h.length(), e_h.length()) - 2; auto h = T(1)/(N+1); for (auto [i, ei] : e_h) { ei = u(i*h) - u_h(i); } } //------------------------------------------------------------------------------ template < typename T, template<typename> class VectorU, template<typename> class VectorF, template<typename> class VectorR, Require< Dense<VectorF<T>>, Dense<VectorU<T>>, Dense<VectorR<T>> > = true > void evalResidual(const VectorU<T> &u_h, const VectorF<T> &f_h, VectorR<T> &r_h) { auto N = assertEqual(u_h.length(), r_h.length()) - 2; auto h = T(1)/(N+1); auto h2 = h*h; r_h(0) = r_h(N+1) = 0; for (std::size_t i=1; i<=N; ++i) { r_h(i) = f_h(i) - 1./h2 * (2*u_h(i) - u_h(i-1) - u_h(i+1)); } } //------------------------------------------------------------------------------ template < typename T, template<typename> class VectorR1, template<typename> class VectorR2, Require< Dense<VectorR1<T>>, Dense<VectorR2<T>> > = true > void restriction(const VectorR1<T> &r_h, VectorR2<T> &r_2h) { auto N = r_h.length() -2; auto n = r_2h.length() -2; assert(2*(n+1) == N+1); r_2h(0) = r_2h(n+1) = 0; for (std::size_t i=1; i<=n; ++i) { r_2h(i) = 0.25*r_h(2*i-1) + 0.5*r_h(2*i) + 0.25*r_h(2*i+1); } } //------------------------------------------------------------------------------ template < typename T, template<typename> class VectorU1, template<typename> class VectorU2, Require< Dense<VectorU1<T>>, Dense<VectorU2<T>> > = true > void updateProlongation(const VectorU1<T> &u_2h, VectorU2<T> &u_h) { auto N = u_h.length() -2; auto n = u_2h.length() -2; assert(2*(n+1) == N+1); for (std::size_t i=1; i<=n; ++i) { u_h(2*i-1) += 0.5 * u_2h(i); u_h(2*i ) += 1.0 * u_2h(i); u_h(2*i+1) += 0.5 * u_2h(i); } } //------------------------------------------------------------------------------ template < typename T, template<typename> class Vector, Require< Dense<Vector<T>> > = true > void addNoise(std::size_t k, Vector<T> &x_h) { auto N = x_h.length()-2; auto h = T(1)/(N+1); for (std::size_t i=1; i<=N; ++i) { x_h(i) += sin(M_PI*k*i*h); } } //------------------------------------------------------------------------------ template < typename T, template<typename> class Vector, Require< Dense<Vector<T>> > = true > T lInfNorm(const Vector<T> &x_h) { T res = 0; for (auto [i, xi] : x_h) { i=i; if (std::abs(xi) > res) { res = std::abs(xi); } } return res; } template < typename T, template<typename> class Vector1, template<typename> class Vector2, Require< Dense<Vector1<T>>, Dense<Vector2<T>> > = true > T lInfNorm(const Vector1<T> &x1_h, const Vector2<T> &x2_h) { auto N = assertEqual(x1_h.length(), x2_h.length()); T res = 0; for (std::size_t i=0; i<N; ++i) { if (std::abs(x1_h(i) - x2_h(i)) > res) { res = std::abs(x1_h(i) - x2_h(i)); } } return res; } //------------------------------------------------------------------------------ template < typename T, template<typename> class Vector, Require< Dense<Vector<T>> > = true > T l1Norm(const Vector<T> &x_h) { auto N = x_h.length()-2; auto h = T(1)/(N+1); T res = 0; for (std::size_t i=1; i<=N; ++i) { i=i; res += 0.5*(std::abs(x_h(i-1)) + std::abs(x_h(i))); } return h*res; } //------------------------------------------------------------------------------ template < typename T, template<typename> class VectorF, template<typename> class VectorU, template<typename> class VectorS, Require< Dense<VectorF<T>>, Dense<VectorU<T>>, Dense<VectorS<T>> > = true > void jacobi_step(double omega, const VectorF<T> &f_h, const VectorU<T> &u_h, VectorS<T> &s_h) { auto N = assertEqual(f_h.length(), u_h.length()) - 2; auto h = T(1)/(N+1); auto h2 = h*h; for (std::size_t i=1; i<=N; ++i) { s_h(i) = (1-omega)*u_h(i) + omega*0.5*(u_h(i-1) +u_h(i+1)+h2*f_h(i)); } s_h(0) = u_h(0); s_h(N+1) = u_h(N+1); } //------------------------------------------------------------------------------ template < typename T, template<typename> class VectorF, template<typename> class VectorU, Require< Dense<VectorF<T>>, Dense<VectorU<T>> > = true > void direct_solver(const VectorF<T> &f_h, VectorU<T> &u_h) { auto N = assertEqual(f_h.length(), u_h.length()) - 2; auto h = T(1)/(N+1); auto h2 = h*h; // forward substitution u_h(1) = h2*f_h(1) + u_h(0); for (std::size_t k=2; k<N; ++k) { u_h(k) = h2*f_h(k) + u_h(k-1)*(k-1)/k; } u_h(N) = h2*f_h(N) + u_h(N+1) + u_h(N-1)*(N-1)/N; // backward substitution u_h(N) = u_h(N)*N/(N+1); for (std::size_t k=N-1; k>=1; --k) { u_h(k) = (u_h(k)+u_h(k+1))*k/(k+1); } } //------------------------------------------------------------------------------ template <typename T, template<typename> class Vector> void init_zero(Vector<T> &u_h) { for (auto [i, ui] : u_h) { i = i; ui = T(0); } } //------------------------------------------------------------------------------ template <typename T, template<typename> class Vector> void print_record(const Vector<T> &r_h, const char *descr) { auto N = r_h.length() -2; fmt::printf("\"%s\"\n", descr); for (auto [i, ri] : r_h) { fmt::printf("%f %f\n", double(i)/(N+1), ri); } fmt::printf("\n\n"); } int main() { /* auto f = [](double x) { return 0; }; auto g = [](double x) { return 0; }; auto u = [](double x) { return 0; }; */ /* auto f = [](double x) { return -2; }; auto g = [](double x) { return 1 + 3*x; }; auto u = [](double x) { return (x+1)*(x+1); }; */ auto f = [](double x) { return -8*M_PI*cos(2*M_PI*exp(2*x)) +16*M_PI*M_PI*exp(2*x)*exp(2*x)*sin(2*M_PI*exp(2*x)); }; auto g = [](double x) { return x*sin(2*M_PI*exp(2.)); }; auto u = [](double x) { return sin(2*M_PI*exp(2*x)); }; std::size_t N0 = 512; std::size_t N1 = 256; std::size_t N2 = 128; std::size_t N3 = 64; DenseVector<double> f_h(N0+1), u_h(N0+1), tmp_h(N0+1), r_h(N0+1), e_h(N0+1); DenseVector<double> f_2h(N1+1), u_2h(N1+1), tmp_2h(N1+1), r_2h(N1+1); DenseVector<double> f_4h(N2+1), u_4h(N2+1), tmp_4h(N2+1), r_4h(N2+1); DenseVector<double> f_8h(N3+1), u_8h(N3+1), tmp_8h(N3+1), r_8h(N3+1); discretizeProblem(f, g, f_h, u_h); // First initialize u_h with exact solution direct_solver(f_h, u_h); // then add some noise addNoise((N0-2)/16, u_h); addNoise((N0-2)/4, u_h); addNoise((N0-2)-3, u_h); addNoise((N0-2)-1, u_h); double omega = 0.5; // smoothing parameter evalResidual(u_h, f_h, r_h); print_record(r_h, "Initial residual"); for (int it=0; it<5; ++it) { // On Omega_{h} // ------------ jacobi_step(omega, f_h, u_h, tmp_h); jacobi_step(omega, f_h, tmp_h, u_h); evalResidual(u_h, f_h, r_h); // On Omega_{2h} // ------------- restriction(r_h, f_2h); init_zero(u_2h); evalResidual(u_2h, f_2h, r_2h); jacobi_step(omega, f_2h, u_2h, tmp_2h); jacobi_step(omega, f_2h, tmp_2h, u_2h); evalResidual(u_2h, f_2h, r_2h); // On Omega_{4h} // ------------- restriction(r_2h, f_4h); init_zero(u_4h); evalResidual(u_4h, f_4h, r_4h); jacobi_step(omega, f_4h, u_4h, tmp_4h); jacobi_step(omega, f_4h, tmp_4h, u_4h); evalResidual(u_4h, f_4h, r_4h); // On Omega_{8h} // ------------- restriction(r_4h, f_8h); direct_solver(f_8h, u_8h); // On Omega_{4h} // ------------- updateProlongation(u_8h, u_4h); jacobi_step(omega, f_4h, u_4h, tmp_4h); jacobi_step(omega, f_4h, tmp_4h, u_4h); // On Omega_{2h} // ------------- updateProlongation(u_4h, u_2h); jacobi_step(omega, f_2h, u_2h, tmp_2h); jacobi_step(omega, f_2h, tmp_2h, u_2h); // On Omega_{h} // ------------ updateProlongation(u_2h, u_h); jacobi_step(omega, f_h, u_h, tmp_h); jacobi_step(omega, f_h, tmp_h, u_h); evalResidual(u_h, f_h, r_h); std::stringstream ss; ss << "r_{h} after " << (it+1) << " V-cycle(s)"; print_record(r_h, ss.str().c_str()); } evalError(u, u_h, e_h); print_record(e_h, "Error e_h = u - u_h after last V-cycle"); print_record(u_h, "u_h after last V-cycle"); }
theon$ g++ -Wall -I /home/numerik/pub/hpc/ws18/session25/ -std=c++17 -o mg_with_4_levels mg_with_4_levels.cpp theon$ ./mg_with_4_levels > mg_with_4_levels.data theon$
Plotting results
theon$ gnuplot mg_with_4_levels.gnuplot theon$ gnuplot mg_with_4_levels2.gnuplot theon$ gnuplot mg_with_4_levels3.gnuplot theon$ gnuplot mg_with_4_levels4.gnuplot theon$ gnuplot mg_with_4_levels5.gnuplot theon$ gnuplot mg_with_4_levels6.gnuplot theon$ gnuplot mg_with_4_levels7.gnuplot theon$