Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GMRES Tutorial #143

Merged
merged 20 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 214 additions & 0 deletions ExampleCodes/LinearSolvers/GMRES/Poisson/AMReX_GMRES_Poisson.H
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
#ifndef AMREX_GMRES_POISSON_H_
#define AMREX_GMRES_POISSON_H_
#include <AMReX_Config.H>

#include <AMReX_GMRES.H>
#include <utility>

namespace amrex {

/**
* \brief Solve Poisson's equation using amrex GMRES class
*/
class GMRESPOISSON
{
public:
using RT = amrex::Real; // double or float
using GM = GMRES<MultiFab,GMRESPOISSON>;

explicit GMRESPOISSON (const BoxArray& ba, const DistributionMapping& dm, const Geometry& geom);

/**
* \brief Solve the linear system
*
* \param a_sol unknowns, i.e., x in A x = b.
* \param a_rhs RHS, i.e., b in A x = b.
* \param a_tol_rel relative tolerance.
* \param a_tol_abs absolute tolerance.
*/
void solve (MultiFab& a_sol, MultiFab const& a_rhs, RT a_tol_rel, RT a_tol_abs);

//! Sets verbosity.
void setVerbose (int v) { m_gmres.setVerbose(v); }

//! Get the GMRES object.
GM& getGMRES () { return m_gmres; }

//! Make MultiFab without ghost cells
MultiFab makeVecRHS () const;

//! Make MultiFab with ghost cells and set ghost cells to zero
MultiFab makeVecLHS () const;

RT norm2 (MultiFab const& mf) const;

static void scale (MultiFab& mf, RT scale_factor);

RT dotProduct (MultiFab const& mf1, MultiFab const& mf2) const;

//! lhs = 0
static void setToZero (MultiFab& lhs);

//! lhs = rhs
static void assign (MultiFab& lhs, MultiFab const& rhs);

//! lhs += a*rhs
static void increment (MultiFab& lhs, MultiFab const& rhs, RT a);

//! lhs = a*rhs_a + b*rhs_b
static void linComb (MultiFab& lhs, RT a, MultiFab const& rhs_a, RT b, MultiFab const& rhs_b);

//! lhs = L(rhs)
void apply (MultiFab& lhs, MultiFab& rhs) const;

void precond (MultiFab& lhs, MultiFab const& rhs) const;

//! Control whether or not to use MLMG as preconditioner.
bool usePrecond (bool new_flag) { return std::exchange(m_use_precond, new_flag); }

private:
GM m_gmres;
BoxArray m_ba;
DistributionMapping m_dm;
Geometry m_geom;
bool m_use_precond;

// store the original RHS needed for Jacobi preconditioner
MultiFab orig_rhs;
};

GMRESPOISSON::GMRESPOISSON (const BoxArray& ba, const DistributionMapping& dm, const Geometry& geom)
: m_ba(ba), m_dm(dm), m_geom(geom)
{
m_gmres.define(*this);
}

auto GMRESPOISSON::makeVecRHS () const -> MultiFab
{
return MultiFab(m_ba, m_dm, 1, 0);
}

auto GMRESPOISSON::makeVecLHS () const -> MultiFab
{
return MultiFab(m_ba, m_dm, 1, 1);
}

auto GMRESPOISSON::norm2 (MultiFab const& mf) const -> RT
{
return mf.norm2();
}

void GMRESPOISSON::scale (MultiFab& mf, RT scale_factor)
{
mf.mult(scale_factor);
}

auto GMRESPOISSON::dotProduct (MultiFab const& mf1, MultiFab const& mf2) const -> RT
{
return MultiFab::Dot(mf1,0,mf2,0,1,0);
}

void GMRESPOISSON::setToZero (MultiFab& lhs)
{
lhs.setVal(0.);
}

void GMRESPOISSON::assign (MultiFab& lhs, MultiFab const& rhs)
{
MultiFab::Copy(lhs,rhs,0,0,1,0);
}

void GMRESPOISSON::increment (MultiFab& lhs, MultiFab const& rhs, RT a)
{
MultiFab::Saxpy(lhs,a,rhs,0,0,1,0);
}

void GMRESPOISSON::linComb (MultiFab& lhs, RT a, MultiFab const& rhs_a, RT b, MultiFab const& rhs_b)
{
MultiFab::LinComb(lhs,a,rhs_a,0,b,rhs_b,0,0,1,0);
}

void GMRESPOISSON::apply (MultiFab& lhs, MultiFab& rhs) const
{
// apply matrix to rhs for output lhs
rhs.FillBoundary(m_geom.periodicity());

const GpuArray<Real, AMREX_SPACEDIM> dx = m_geom.CellSizeArray();

for ( MFIter mfi(lhs,TilingIfNotGPU()); mfi.isValid(); ++mfi ) {

const Box& bx = mfi.tilebox();

const Array4<const Real> & rhs_p = rhs.array(mfi);
const Array4< Real> & lhs_p = lhs.array(mfi);

amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
{
lhs_p(i,j,k) = ( rhs_p(i+1,j,k) - 2.*rhs_p(i,j,k) + rhs_p(i-1,j,k) ) / (dx[0]*dx[0])
+ ( rhs_p(i,j+1,k) - 2.*rhs_p(i,j,k) + rhs_p(i,j-1,k) ) / (dx[1]*dx[1])
#if (AMREX_SPACEDIM == 3)
+ ( rhs_p(i,j,k+1) - 2.*rhs_p(i,j,k) + rhs_p(i,j,k-1) ) / (dx[2]*dx[2])
#endif
;
});

}


}

// applies preconditioner to rhs. If there is no preconditioner,
// this function should do lhs = rhs.
void GMRESPOISSON::precond (MultiFab& lhs, MultiFab const& rhs) const
{
if (m_use_precond) {
const GpuArray<Real, AMREX_SPACEDIM> dx = m_geom.CellSizeArray();
amrex::Real fac = 0.;
for (int d=0; d<AMREX_SPACEDIM; ++d) { fac -= 2./(dx[d]*dx[d]); }

MultiFab tmp(lhs.boxArray(), lhs.DistributionMap(), 1, 1);
auto const& tmp_ma = tmp.const_arrays();
auto const& rh_ma = rhs.const_arrays();
auto const& x_ma = lhs.arrays();

const int niters = 8;
for (int iter = 0; iter < niters; ++iter) {
if (iter == 0) {
ParallelFor(lhs, [=] AMREX_GPU_DEVICE (int b, int i, int j, int k)
{
x_ma[b](i,j,k) = rh_ma[b](i,j,k) / fac;
});
} else {
MultiFab::Copy(tmp, lhs, 0, 0, 1, 0);
tmp.FillBoundary(m_geom.periodicity());
ParallelFor(lhs, [=] AMREX_GPU_DEVICE (int b, int i, int j, int k)
{
auto const& phi = tmp_ma[b];
auto ax = ( phi(i+1,j,k) - 2.*phi(i,j,k) + phi(i-1,j,k) ) / (dx[0]*dx[0])
+ ( phi(i,j+1,k) - 2.*phi(i,j,k) + phi(i,j-1,k) ) / (dx[1]*dx[1])
#if (AMREX_SPACEDIM == 3)
+ ( phi(i,j,k+1) - 2.*phi(i,j,k) + phi(i,j,k-1) ) / (dx[2]*dx[2])
#endif
;
auto res = rh_ma[b](i,j,k) - ax;
x_ma[b](i,j,k) += res / fac * Real(2./3.); // 2/3: weighted jacobi
});
}
Gpu::streamSynchronize();
}
} else {
MultiFab::Copy(lhs,rhs,0,0,1,0);
}
}

void GMRESPOISSON::solve (MultiFab& a_sol, MultiFab const& a_rhs, RT a_tol_rel, RT a_tol_abs)
{
orig_rhs.define(m_ba,m_dm,1,0);
MultiFab::Copy(orig_rhs,a_rhs,0,0,1,0);

m_gmres.solve(a_sol, a_rhs, a_tol_rel, a_tol_abs);
}

}

#endif
17 changes: 17 additions & 0 deletions ExampleCodes/LinearSolvers/GMRES/Poisson/GNUmakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# AMREX_HOME defines the directory in which we will find all the AMReX code.
AMREX_HOME ?= ../../../../../amrex

DEBUG = FALSE
USE_MPI = FALSE
USE_OMP = FALSE
COMP = gnu
DIM = 3

include $(AMREX_HOME)/Tools/GNUMake/Make.defs

include ./Make.package

include $(AMREX_HOME)/Src/Base/Make.package
include $(AMREX_HOME)/Src/LinearSolvers/Make.package

include $(AMREX_HOME)/Tools/GNUMake/Make.rules
2 changes: 2 additions & 0 deletions ExampleCodes/LinearSolvers/GMRES/Poisson/Make.package
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CEXE_sources += main.cpp
CEXE_headers += AMReX_GMRES_Poisson.H
4 changes: 4 additions & 0 deletions ExampleCodes/LinearSolvers/GMRES/Poisson/inputs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
n_cell = 32
max_grid_size = 16

use_precond = 0
131 changes: 131 additions & 0 deletions ExampleCodes/LinearSolvers/GMRES/Poisson/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* A simplified usage of the AMReX GMRES class
*/

#include <AMReX.H>
#include <AMReX_PlotFileUtil.H>
#include <AMReX_ParmParse.H>
#include <AMReX_GMRES_Poisson.H>

int main (int argc, char* argv[])
{
amrex::Initialize(argc,argv);
{

// **********************************
// DECLARE SIMULATION PARAMETERS
// **********************************

// number of cells on each side of the domain
int n_cell;

// size of each box (or grid)
int max_grid_size;

// use preconditioner?
int use_precond;

// **********************************
// READ PARAMETER VALUES FROM INPUT DATA
// **********************************
// inputs parameters
{
// ParmParse is way of reading inputs from the inputs file
// pp.get means we require the inputs file to have it
// pp.query means we optionally need the inputs file to have it - but we must supply a default here
amrex::ParmParse pp;

// We need to get n_cell from the inputs file - this is the number of cells on each side of
// a square (or cubic) domain.
pp.get("n_cell",n_cell);

// The domain is broken into boxes of size max_grid_size
pp.get("max_grid_size",max_grid_size);

use_precond = 0;
pp.query("use_precond",use_precond);
}

// **********************************
// DEFINE SIMULATION SETUP AND GEOMETRY
// **********************************

// make BoxArray and Geometry
// ba will contain a list of boxes that cover the domain
// geom contains information such as the physical domain size,
// number of points in the domain, and periodicity
amrex::BoxArray ba;
amrex::Geometry geom;

// define lower and upper indices
amrex::IntVect dom_lo( 0, 0, 0);
amrex::IntVect dom_hi(n_cell-1, n_cell-1, n_cell-1);

// Make a single box that is the entire domain
amrex::Box domain(dom_lo, dom_hi);

// Initialize the boxarray "ba" from the single box "domain"
ba.define(domain);

// Break up boxarray "ba" into chunks no larger than "max_grid_size" along a direction
ba.maxSize(max_grid_size);

// This defines the physical box, [0,1] in each direction.
amrex::RealBox real_box({ 0., 0., 0.},
{ 1., 1., 1.});

// periodic in all direction
amrex::Array<int,3> is_periodic{1,1,1};

// This defines a Geometry object
geom.define(domain, real_box, amrex::CoordSys::cartesian, is_periodic);

// How Boxes are distrubuted among MPI processes
amrex::DistributionMapping dm(ba);

// we allocate two phi multifabs; one will store the old state, the other the new.
amrex::MultiFab rhs(ba, dm, 1, 0);
amrex::MultiFab phi(ba, dm, 1, 1);

// **********************************
// INITIALIZE DATA LOOP
// **********************************

// loop over boxes
for (amrex::MFIter mfi(rhs); mfi.isValid(); ++mfi)
{
const amrex::Box& bx = mfi.validbox();

const amrex::Array4<amrex::Real>& rhs_p = rhs.array(mfi);

// fill rhs with random numbers
amrex::ParallelForRNG(bx, [=] AMREX_GPU_DEVICE(int i, int j, int k, amrex::RandomEngine const& engine) noexcept
{
rhs_p(i,j,k) = amrex::RandomNormal(0.,1.,engine);
});
}

// offset data so the rhs sums to zero
amrex::Real sum = rhs.sum();
amrex::Long npts = rhs.boxArray().numPts();
rhs.plus(-sum/npts,0,1);

WriteSingleLevelPlotfile("rhs", rhs, {"rhs"}, geom, 0., 0);

amrex::GMRESPOISSON gmres_poisson(ba,dm,geom);

// initial guess
phi.setVal(0.);

gmres_poisson.usePrecond(use_precond);
gmres_poisson.setVerbose(2);
gmres_poisson.solve(phi, rhs, 1.e-12, 0.);

WriteSingleLevelPlotfile("phi", phi, {"phi"}, geom, 0., 0);

}
amrex::Finalize();
return 0;
}


Loading