/* mg_solver.hh - This file is part of MUSIC - a code to generate multi-scale initial conditions for cosmological simulations Copyright (C) 2010 Oliver Hahn */ #ifndef __MG_SOLVER_HH #define __MG_SOLVER_HH #include #include #include "mg_operators.hh" #include "mg_interp.hh" #include "mesh.hh" #define BEGIN_MULTIGRID_NAMESPACE namespace multigrid { #define END_MULTIGRID_NAMESPACE } BEGIN_MULTIGRID_NAMESPACE //! options for multigrid smoothing operation namespace opt { enum smtype { sm_jacobi, sm_gauss_seidel, sm_sor }; } //! actual implementation of FAS adaptive multigrid solver template< class S, class I, class O, typename T=double > class solver { public: typedef S scheme; typedef O mgop; typedef I interp; protected: scheme m_scheme; //!< finite difference scheme mgop m_gridop; //!< grid prolongation and restriction operator unsigned m_npresmooth, //!< number of pre sweeps m_npostsmooth; //!< number of post sweeps opt::smtype m_smoother; //!< smoothing method to be applied unsigned m_ilevelmin; //!< index of the top grid level const static bool m_bperiodic = true; //!< flag whether top grid is periodic std::vector m_residu_ini; //!< vector of initial residuals for each level bool m_is_ini; //!< bool that is true for first iteration GridHierarchy *m_pu, //!< pointer to GridHierarchy for solution u *m_pf, //!< pointer to GridHierarchy for right-hand-side *m_pfsave; //!< pointer to saved state of right-hand-side (unused) const MeshvarBnd *m_pubnd; //! compute residual for a level double compute_error( const MeshvarBnd& u, const MeshvarBnd& unew, int ilevel ); //! compute residuals for entire grid hierarchy double compute_error( const GridHierarchy& uh, const GridHierarchy& uhnew, bool verbose ); //! compute residuals for entire grid hierarchy double compute_RMS_resid( const GridHierarchy& uh, const GridHierarchy& fh, bool verbose ); protected: //! Jacobi smoothing void Jacobi( T h, MeshvarBnd* u, const MeshvarBnd* f ); //! Gauss-Seidel smoothing void GaussSeidel( T h, MeshvarBnd* u, const MeshvarBnd* f ); //! Successive-Overrelaxation smoothing void SOR( T h, MeshvarBnd* u, const MeshvarBnd* f ); //! main two-grid (V-cycle) for multi-grid iterations void twoGrid( unsigned ilevel ); //! apply boundary conditions void setBC( unsigned ilevel ); //! make top grid periodic boundary conditions void make_periodic( MeshvarBnd *u ); //void interp_coarse_fine_cubic( unsigned ilevel, MeshvarBnd& coarse, MeshvarBnd& fine ); public: //! constructor solver( GridHierarchy& f, opt::smtype smoother, unsigned npresmooth, unsigned npostsmooth ); //! destructor ~solver() { } //! solve Poisson's equation double solve( GridHierarchy& u, double accuracy, double h=-1.0, bool verbose=false ); //! solve Poisson's equation double solve( GridHierarchy& u, double accuracy, bool verbose=false ) { return this->solve ( u, accuracy, -1.0, verbose ); } }; template< class S, class I, class O, typename T > solver::solver( GridHierarchy& f, opt::smtype smoother, unsigned npresmooth, unsigned npostsmooth ) : m_scheme(), m_gridop(), m_npresmooth( npresmooth ), m_npostsmooth( npostsmooth ), m_smoother( smoother ), m_ilevelmin( f.levelmin() ), m_is_ini( true ), m_pf( &f ) { m_is_ini = true; } template< class S, class I, class O, typename T > void solver::Jacobi( T h, MeshvarBnd *u, const MeshvarBnd* f ) { int nx = u->size(0), ny = u->size(1), nz = u->size(2); double c0 = -1.0/m_scheme.ccoeff(), h2 = h*h; MeshvarBnd uold(*u); double alpha = 0.95, ialpha = 1.0-alpha; #pragma omp parallel for for( int ix=0; ix void solver::SOR( T h, MeshvarBnd *u, const MeshvarBnd* f ) { int nx = u->size(0), ny = u->size(1), nz = u->size(2); double c0 = -1.0/m_scheme.ccoeff(), h2 = h*h; MeshvarBnd uold(*u); double alpha = 1.2, //alpha = 2 / (1 + 4 * atan(1.0) / double(u->size(0)))-1.0, //.. ideal alpha ialpha = 1.0-alpha; #pragma omp parallel for for( int ix=0; ix void solver::GaussSeidel( T h, MeshvarBnd* u, const MeshvarBnd* f ) { int nx = u->size(0), ny = u->size(1), nz = u->size(2); T c0 = -1.0/m_scheme.ccoeff(), h2 = h*h; for( int color=0; color < 2; ++color ){ MeshvarBnd uold(*u); #pragma omp parallel for for( int ix=0; ix void solver::twoGrid( unsigned ilevel ) { MeshvarBnd *uf, *uc, *ff, *fc; double h = 1.0/(1<get_grid(ilevel); ff = m_pf->get_grid(ilevel); uc = m_pu->get_grid(ilevel-1); fc = m_pf->get_grid(ilevel-1); int nx = uf->size(0), ny = uf->size(1), nz = uf->size(2); if( m_bperiodic && ilevel <= m_ilevelmin) make_periodic( uf ); else if(!m_bperiodic) setBC( ilevel ); //... do smoothing sweeps with specified solver for( unsigned i=0; i m_ilevelmin ) interp().interp_coarse_fine(ilevel,*uc,*uf); if( m_smoother == opt::sm_gauss_seidel ) GaussSeidel( h, uf, ff ); else if( m_smoother == opt::sm_jacobi ) Jacobi( h, uf, ff); else if( m_smoother == opt::sm_sor ) SOR( h, uf, ff ); if( m_bperiodic && ilevel <= m_ilevelmin ) make_periodic( uf ); } m_gridop.restrict( *uf, *uc ); //... essential!! if( m_bperiodic && ilevel <= m_ilevelmin ) make_periodic( uc ); else if( ilevel > m_ilevelmin ) interp().interp_coarse_fine(ilevel,*uc,*uf); //.................................................................... //... we now use hard-coded restriction+operatore app, see below /*meshvar_bnd Lu(*uf,false); Lu.zero(); #pragma omp parallel for for( int ix=0; ixoffset(0), oyp = uf->offset(1), ozp = uf->offset(2); meshvar_bnd tLu(*uc,false); #pragma omp parallel for for( int ix=0; ixoffset(0); oj = ff->offset(1); ok = ff->offset(2); #pragma omp parallel for for( int ix=oi; ixsize(0)/2; ++ix ) for( int iy=oj; iysize(1)/2; ++iy ) for( int iz=ok; izsize(2)/2; ++iz ) (*fc)(ix,iy,iz) += ((tLu( ix, iy, iz ) - (m_scheme.apply( *uc, ix, iy, iz )/(4.0*h2)))); tLu.deallocate(); meshvar_bnd ucsave(*uc,true); //... have we reached the end of the recursion or do we need to go up one level? if( ilevel == 1 ) if( m_bperiodic ) (*uc)(0,0,0) = 0.0; else (*uc)(0,0,0) = (m_scheme.rhs( (*uc), 0, 0, 0 ) + 4.0 * h2 * (*fc)(0,0,0))*c0; else twoGrid( ilevel-1 ); meshvar_bnd cc(*uc,false); //... compute correction on coarse grid #pragma omp parallel for for( int ix=0; ix<(int)cc.size(0); ++ix ) for( int iy=0; iy<(int)cc.size(1); ++iy ) for( int iz=0; iz<(int)cc.size(2); ++iz ) cc(ix,iy,iz) = (*uc)(ix,iy,iz) - ucsave(ix,iy,iz); ucsave.deallocate(); if( m_bperiodic && ilevel <= m_ilevelmin ) make_periodic( &cc ); m_gridop.prolong_add( cc, *uf ); //... interpolate and apply coarse-fine boundary conditions on fine level if( m_bperiodic && ilevel <= m_ilevelmin ) make_periodic( uf ); else if(!m_bperiodic) setBC( ilevel ); //... do smoothing sweeps with specified solver for( unsigned i=0; i m_ilevelmin ) interp().interp_coarse_fine(ilevel,*uc,*uf); if( m_smoother == opt::sm_gauss_seidel ) GaussSeidel( h, uf, ff ); else if( m_smoother == opt::sm_jacobi ) Jacobi( h, uf, ff); else if( m_smoother == opt::sm_sor ) SOR( h, uf, ff ); if( m_bperiodic && ilevel <= m_ilevelmin ) make_periodic( uf ); } } template< class S, class I, class O, typename T > double solver::compute_error( const MeshvarBnd& u, const MeshvarBnd& f, int ilevel ) { int nx = u.size(0), ny = u.size(1), nz = u.size(2); double err = 0.0, err2 = 0.0; size_t count = 0; double h = 1.0/(1ul< 0.0 )//&& u(ix,iy,iz) != unew(ix,iy,iz) ) { //err += fabs(1.0 - (double)u(ix,iy,iz)/(double)unew(ix,iy,iz)); /*err += fabs(((double)m_scheme.apply( u, ix, iy, iz )/h2 + (double)(f(ix,iy,iz)) )); err2 += fabs((double)f(ix,iy,iz));*/ err += fabs( (double)m_scheme.apply( u, ix, iy, iz )/h2/(double)(f(ix,iy,iz)) + 1.0 ); ++count; } if( count != 0 ) err /= count; return err; } template< class S, class I, class O, typename T > double solver::compute_error( const GridHierarchy& uh, const GridHierarchy& fh, bool verbose ) { double maxerr = 0.0; for( unsigned ilevel=uh.levelmin(); ilevel <= uh.levelmax(); ++ilevel ) { int nx = uh.get_grid(ilevel)->size(0), ny = uh.get_grid(ilevel)->size(1), nz = uh.get_grid(ilevel)->size(2); double err = 0.0, mean_res = 0.0; size_t count = 0; double h = 1.0/(1ul< 0.0 ) { err += fabs( res/val ); mean_res += fabs(res); ++count; } } if( count != 0 ) { err /= count; mean_res /= count; } if( verbose ) std::cout << " Level " << std::setw(6) << ilevel << ", Error = " << err << std::endl; LOGDEBUG("[mg] level %3d, residual %g, rel. error %g",ilevel, mean_res, err); maxerr = std::max(maxerr,err); } return maxerr; } template< class S, class I, class O, typename T > double solver::compute_RMS_resid( const GridHierarchy& uh, const GridHierarchy& fh, bool verbose ) { if( m_is_ini ) m_residu_ini.assign( uh.levelmax()+1, 0.0 ); double maxerr=0.0; for( unsigned ilevel=uh.levelmin(); ilevel <= uh.levelmax(); ++ilevel ) { int nx = uh.get_grid(ilevel)->size(0), ny = uh.get_grid(ilevel)->size(1), nz = uh.get_grid(ilevel)->size(2); double h = 1.0/(1< maxerr ) maxerr = err_rel; } if( m_is_ini ) m_is_ini = false; return maxerr; } template< class S, class I, class O, typename T > double solver::solve( GridHierarchy& uh, double acc, double h, bool verbose ) { double err, maxerr = 1e30; unsigned niter = 0; bool fullverbose = false; m_pu = &uh; //err = compute_RMS_resid( *m_pu, *m_pf, fullverbose ); //... iterate ...// while (true) { LOGUSER("Performing multi-grid V-cycle..."); twoGrid( uh.levelmax() ); //err = compute_RMS_resid( *m_pu, *m_pf, fullverbose ); err = compute_error( *m_pu, *m_pf, fullverbose ); ++niter; if( fullverbose ){ LOGUSER(" multigrid iteration %3d, maximum RMS residual = %g", niter, err ); std::cout << " - Step No. " << std::setw(3) << niter << ", Max Err = " << err << std::endl; std::cout << " ---------------------------------------------------\n"; } if( err < maxerr ) maxerr = err; if( (niter > 1) && ((err < acc) || (niter > 20)) ) break; } if( err > acc ) { std::cout << "Error : no convergence in Poisson solver" << std::endl; LOGERR("No convergence in Poisson solver, final error: %g.",err); } else if( verbose ) { std::cout << " - Converged in " << niter << " steps to " << maxerr << std::endl; LOGUSER("Poisson solver converged to max. error of %g in %d steps.",err,niter); } //.. make sure that the RHS does not contain the FAS corrections any more for( int i=m_pf->levelmax(); i>0; --i ) m_gridop.restrict( *m_pf->get_grid(i), *m_pf->get_grid(i-1) ); return err; } //TODO: this only works for 2nd order! (but actually not needed) template< class S, class I, class O, typename T > void solver::setBC( unsigned ilevel ) { //... set only on level before additional refinement starts if( ilevel == m_ilevelmin ) { MeshvarBnd *u = m_pu->get_grid(ilevel); int nx = u->size(0), ny = u->size(1), nz = u->size(2); for( int iy=0; iy void solver::make_periodic( MeshvarBnd *u ) { int nx = u->size(0), ny = u->size(1), nz = u->size(2); int nb = u->m_nbnd; //if( u->offset(0) == 0 ) for( int iy=-nb; iyoffset(1) == 0 ) for( int ix=-nb; ixoffset(2) == 0 ) for( int ix=-nb; ix