Files
mfem/linalg/ginkgo.cpp
T
2020-03-09 15:36:41 -07:00

486 lines
16 KiB
C++

// Copyright (c) 2010-2020, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "../config/config.hpp"
#ifdef MFEM_USE_GINKGO
#include "ginkgo.hpp"
#include "sparsemat.hpp"
#include "../general/globals.hpp"
#include "../general/error.hpp"
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <cmath>
namespace mfem
{
namespace GinkgoWrappers
{
GinkgoIterativeSolverBase::GinkgoIterativeSolverBase(
const std::string &exec_type, int print_iter, int max_num_iter,
double RTOLERANCE, double ATOLERANCE)
: exec_type(exec_type),
print_lvl(print_iter),
max_iter(max_num_iter),
rel_tol(sqrt(RTOLERANCE)),
abs_tol(sqrt(ATOLERANCE))
{
if (exec_type == "reference")
{
executor = gko::ReferenceExecutor::create();
}
else if (exec_type == "omp")
{
executor = gko::OmpExecutor::create();
}
else if (exec_type == "cuda" && gko::CudaExecutor::get_num_devices() > 0)
{
executor = gko::CudaExecutor::create(0, gko::OmpExecutor::create());
}
else
{
mfem::err <<
" exec_type needs to be one of the three strings: \"reference\", \"cuda\" or \"omp\" "
<< std::endl;
}
using ResidualCriterionFactory = gko::stop::ResidualNormReduction<>;
residual_criterion = ResidualCriterionFactory::build()
.with_reduction_factor(rel_tol)
.on(executor);
combined_factory =
gko::stop::Combined::build()
.with_criteria(residual_criterion,
gko::stop::Iteration::build()
.with_max_iters(max_iter)
.on(executor))
.on(executor);
}
void
GinkgoIterativeSolverBase::initialize_ginkgo_log(gko::matrix::Dense<double>* b)
{
// Add the logger object. See the different masks available in Ginkgo's
// documentation
convergence_logger = gko::log::Convergence<>::create(
executor, gko::log::Logger::criterion_check_completed_mask);
residual_logger = std::make_shared<ResidualLogger<>>(executor,
gko::lend(system_matrix),b);
}
void
GinkgoIterativeSolverBase::apply(Vector &solution,
const Vector &rhs)
{
// some shortcuts.
using val_array = gko::Array<double>;
using vec = gko::matrix::Dense<double>;
MFEM_VERIFY(system_matrix, "System matrix not initialized");
MFEM_VERIFY(executor, "executor is not initialized");
MFEM_VERIFY(rhs.Size() == solution.Size(),
"Mismatching sizes for rhs and solution");
// Create the rhs vector in Ginkgo's format.
std::vector<double> f(rhs.Size());
std::copy(rhs.GetData(), rhs.GetData() + rhs.Size(), f.begin());
auto b =
vec::create(executor,
gko::dim<2>(rhs.Size(), 1),
val_array::view(executor->get_master(), rhs.Size(), f.data()),
1);
// Create the solution vector in Ginkgo's format.
std::vector<double> u(solution.Size());
std::copy(solution.GetData(), solution.GetData() + solution.Size(), u.begin());
auto x = vec::create(executor,
gko::dim<2>(solution.Size(), 1),
val_array::view(executor->get_master(),
solution.Size(),
u.data()),
1);
// Create the logger object to log some data from the solvers to confirm
// convergence.
initialize_ginkgo_log(gko::lend(b));
MFEM_VERIFY(convergence_logger, "convergence logger not initialized" );
if (print_lvl==1)
{
MFEM_VERIFY(residual_logger, "residual logger not initialized" );
solver_gen->add_logger(residual_logger);
}
// Generate the solver from the solver using the system matrix.
auto solver = solver_gen->generate(system_matrix);
// Add the convergence logger object to the combined factory to retrieve the
// solver and other data
combined_factory->add_logger(convergence_logger);
// Finally, apply the solver to b and get the solution in x.
solver->apply(gko::lend(b), gko::lend(x));
// The convergence_logger object contains the residual vector after the
// solver has returned. use this vector to compute the residual norm of the
// solution. Get the residual norm from the logger. As the convergence logger
// returns a `linop`, it is necessary to convert it to a Dense matrix.
// Additionally, if the logger is logging on the gpu, it is necessary to copy
// the data to the host and hence the `residual_norm_d_master`
auto residual_norm = convergence_logger->get_residual_norm();
auto residual_norm_d =
gko::as<gko::matrix::Dense<double>>(residual_norm);
auto residual_norm_d_master =
gko::matrix::Dense<double>::create(executor->get_master(),
gko::dim<2> {1, 1});
residual_norm_d_master->copy_from(residual_norm_d);
// Get the number of iterations taken to converge to the solution.
auto num_iteration = convergence_logger->get_num_iterations();
// Ginkgo works with a relative residual norm through its
// ResidualNormReduction criterion. Therefore, to get the normalized
// residual, we divide by the norm of the rhs.
auto b_norm = gko::matrix::Dense<double>::create(executor->get_master(),
gko::dim<2> {1, 1});
if (executor != executor->get_master())
{
auto b_master = vec::create(executor->get_master(),
gko::dim<2>(rhs.Size(), 1),
val_array::view(executor->get_master(),
rhs.Size(),
f.data()),
1);
b_master->compute_norm2(b_norm.get());
}
else
{
b->compute_norm2(b_norm.get());
}
MFEM_VERIFY(b_norm.get()->at(0, 0) != 0.0, " rhs norm is zero");
// Some residual norm and convergence print outs. As both
// `residual_norm_d_master` and `b_norm` are seen as Dense matrices, we use
// the `at` function to get the first value here. In case of multiple right
// hand sides, this will need to be modified.
auto fin_res_norm = std::pow(residual_norm_d_master->at(0,0) / b_norm->at(0,0),
2);
if (num_iteration==max_iter &&
fin_res_norm > rel_tol )
{
converged = 1;
}
if (fin_res_norm < rel_tol)
{
converged =0;
}
if (print_lvl ==1)
{
residual_logger->write();
}
if (converged!=0)
{
mfem::err << "No convergence!" << '\n';
mfem::out << "(B r_N, r_N) = " << fin_res_norm << '\n'
<< "Number of iterations: " << num_iteration << '\n';
}
if (print_lvl >=2 && converged==0 )
{
mfem::out << "Converged in " << num_iteration <<
" iterations with final residual norm "
<< fin_res_norm << '\n';
}
// Check if the solution is on a CUDA device, if so, copy it over to the
// host.
if (executor != executor->get_master())
{
auto x_master = vec::create(executor->get_master(),
gko::dim<2>(solution.Size(), 1),
val_array::view(executor,
solution.Size(),
x->get_values()),
1);
x.reset(x_master.release());
}
// Finally copy over the solution vector to mfem's solution vector.
std::copy(x->get_values(),
x->get_values() + solution.Size(),
solution.GetData());
}
void
GinkgoIterativeSolverBase::initialize(
const SparseMatrix *matrix)
{
// Needs to be a square matrix
MFEM_VERIFY(matrix->Height() == matrix->Width(), "System matrix is not square");
const int N = matrix->Size();
using mtx = gko::matrix::Csr<double, int>;
std::shared_ptr<mtx> system_matrix_compute;
system_matrix_compute = mtx::create(executor->get_master(),
gko::dim<2>(N),
matrix->NumNonZeroElems());
double *mat_values = system_matrix_compute->get_values();
int *mat_row_ptrs = system_matrix_compute->get_row_ptrs();
int *mat_col_idxs = system_matrix_compute->get_col_idxs();
mat_row_ptrs[0] =0;
for (int r=0; r< N; ++r)
{
const int* col = matrix->GetRowColumns(r);
const double * val = matrix->GetRowEntries(r);
mat_row_ptrs[r+1] = mat_row_ptrs[r] + matrix->RowSize(r);
for (int cj=0; cj < matrix->RowSize(r); cj++ )
{
mat_values[mat_row_ptrs[r]+cj] = val[cj];
mat_col_idxs[mat_row_ptrs[r]+cj] = col[cj];
}
}
system_matrix =
mtx::create(executor, gko::dim<2>(N), matrix->NumNonZeroElems());
system_matrix->copy_from(system_matrix_compute.get());
}
void
GinkgoIterativeSolverBase::solve(const SparseMatrix *matrix,
Vector &solution,
const Vector &rhs)
{
initialize(matrix);
apply(solution, rhs);
}
/* ---------------------- CGSolver ------------------------ */
CGSolver::CGSolver(
const std::string &exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using cg = gko::solver::Cg<double>;
this->solver_gen =
cg::build().with_criteria(this->combined_factory).on(this->executor);
}
CGSolver::CGSolver(
const std::string &exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE,
const gko::LinOpFactory* preconditioner
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using cg = gko::solver::Cg<double>;
this->solver_gen = cg::build()
.with_criteria(this->combined_factory)
.with_preconditioner(preconditioner)
.on(this->executor);
}
/* ---------------------- BICGSTABSolver ------------------------ */
BICGSTABSolver::BICGSTABSolver(
const std::string &exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using bicgstab = gko::solver::Bicgstab<double>;
this->solver_gen = bicgstab::build()
.with_criteria(this->combined_factory)
.on(this->executor);
}
BICGSTABSolver::BICGSTABSolver(
const std::string &exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE,
const gko::LinOpFactory* preconditioner
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using bicgstab = gko::solver::Bicgstab<double>;
this->solver_gen = bicgstab::build()
.with_criteria(this->combined_factory)
.with_preconditioner(preconditioner)
.on(this->executor);
}
/* ---------------------- CGSSolver ------------------------ */
CGSSolver::CGSSolver(
const std::string &exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using cgs = gko::solver::Cgs<double>;
this->solver_gen =
cgs::build().with_criteria(this->combined_factory).on(this->executor);
}
CGSSolver::CGSSolver(
const std::string &exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE,
const gko::LinOpFactory* preconditioner
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using cgs = gko::solver::Cgs<double>;
this->solver_gen = cgs::build()
.with_criteria(this->combined_factory)
.with_preconditioner(preconditioner)
.on(this->executor);
}
/* ---------------------- FCGSolver ------------------------ */
FCGSolver::FCGSolver(
const std::string &exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using fcg = gko::solver::Fcg<double>;
this->solver_gen =
fcg::build().with_criteria(this->combined_factory).on(this->executor);
}
FCGSolver::FCGSolver(
const std::string &exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE,
const gko::LinOpFactory* preconditioner
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using fcg = gko::solver::Fcg<double>;
this->solver_gen = fcg::build()
.with_criteria(this->combined_factory)
.with_preconditioner(preconditioner)
.on(this->executor);
}
/* ---------------------- GMRESSolver ------------------------ */
GMRESSolver::GMRESSolver(
const std::string &exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using gmres = gko::solver::Gmres<double>;
this->solver_gen = gmres::build()
.with_krylov_dim(m)
.with_criteria(this->combined_factory)
.on(this->executor);
}
GMRESSolver::GMRESSolver(
const std::string &exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE,
const gko::LinOpFactory* preconditioner
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using gmres = gko::solver::Gmres<double>;
this->solver_gen = gmres::build()
.with_krylov_dim(m)
.with_criteria(this->combined_factory)
.with_preconditioner(preconditioner)
.on(this->executor);
}
/* ---------------------- IRSolver ------------------------ */
IRSolver::IRSolver(
const std::string & exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using ir = gko::solver::Ir<double>;
this->solver_gen =
ir::build().with_criteria(this->combined_factory).on(this->executor);
}
IRSolver::IRSolver(
const std::string &exec_type,
int print_iter,
int max_num_iter,
double RTOLERANCE,
double ATOLERANCE,
const gko::LinOpFactory* inner_solver
)
: GinkgoIterativeSolverBase(exec_type, print_iter, max_num_iter, RTOLERANCE,
ATOLERANCE)
{
using ir = gko::solver::Ir<double>;
this->solver_gen = ir::build()
.with_criteria(this->combined_factory)
.with_solver(inner_solver)
.on(this->executor);
}
} // namespace GinkgoWrappers
} // namespace mfem
#endif // MFEM_USE_GINKGO