opm-simulators/opm/simulators/linalg/ISTLSolverEbos.hpp

629 lines
24 KiB
C++
Raw Normal View History

/*
Copyright 2016 IRIS AS
2020-10-12 10:06:11 -05:00
Copyright 2019, 2020 Equinor ASA
Copyright 2020 SINTEF Digital, Mathematics and Cybernetics
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_ISTLSOLVER_EBOS_HEADER_INCLUDED
#define OPM_ISTLSOLVER_EBOS_HEADER_INCLUDED
#include <dune/istl/owneroverlapcopy.hh>
#include <ebos/eclbasevanguard.hh>
#include <opm/common/ErrorMacros.hpp>
2022-12-13 05:54:27 -06:00
#include <opm/common/Exceptions.hpp>
2023-03-01 06:17:39 -06:00
#include <opm/common/TimingMacros.hpp>
#include <opm/models/discretization/common/fvbaseproperties.hh>
#include <opm/models/common/multiphasebaseproperties.hh>
#include <opm/models/utils/parametersystem.hh>
#include <opm/models/utils/propertysystem.hh>
#include <opm/simulators/flow/BlackoilModelParametersEbos.hpp>
#include <opm/simulators/linalg/ExtractParallelGridInformationToISTL.hpp>
#include <opm/simulators/linalg/FlowLinearSolverParameters.hpp>
#include <opm/simulators/linalg/matrixblock.hh>
#include <opm/simulators/linalg/istlsparsematrixadapter.hh>
#include <opm/simulators/linalg/PreconditionerWithUpdate.hpp>
#include <opm/simulators/linalg/WellOperators.hpp>
#include <opm/simulators/linalg/WriteSystemMatrixHelper.hpp>
#include <opm/simulators/linalg/findOverlapRowsAndColumns.hpp>
#include <opm/simulators/linalg/getQuasiImpesWeights.hpp>
#include <opm/simulators/linalg/setupPropertyTree.hpp>
#include <any>
#include <cstddef>
#include <functional>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
namespace Opm::Properties {
namespace TTag {
struct FlowIstlSolver {
using InheritsFrom = std::tuple<FlowIstlSolverParams>;
};
}
template <class TypeTag, class MyTypeTag>
struct EclWellModel;
//! Set the type of a global jacobian matrix for linear solvers that are based on
//! dune-istl.
2020-08-27 04:38:38 -05:00
template<class TypeTag>
struct SparseMatrixAdapter<TypeTag, TTag::FlowIstlSolver>
{
private:
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
enum { numEq = getPropValue<TypeTag, Properties::NumEq>() };
using Block = MatrixBlock<Scalar, numEq, numEq>;
public:
using type = typename Linear::IstlSparseMatrixAdapter<Block>;
};
} // namespace Opm::Properties
namespace Opm
{
2022-11-17 02:59:49 -06:00
#if COMPILE_BDA_BRIDGE
template<class Matrix, class Vector, int block_size> class BdaBridge;
class WellContributions;
#endif
namespace detail
{
template<class Matrix, class Vector, class Comm>
struct FlexibleSolverInfo
{
using AbstractSolverType = Dune::InverseOperator<Vector, Vector>;
using AbstractOperatorType = Dune::AssembledLinearOperator<Matrix, Vector, Vector>;
using AbstractPreconditionerType = Dune::PreconditionerWithUpdate<Vector, Vector>;
void create(const Matrix& matrix,
bool parallel,
const PropertyTree& prm,
size_t pressureIndex,
std::function<Vector()> trueFunc,
Comm& comm);
std::unique_ptr<AbstractSolverType> solver_;
std::unique_ptr<AbstractOperatorType> op_;
std::unique_ptr<LinearOperatorExtra<Vector,Vector>> wellOperator_;
AbstractPreconditionerType* pre_ = nullptr;
size_t interiorCellNum_ = 0;
};
2022-11-17 02:59:49 -06:00
#if COMPILE_BDA_BRIDGE
template<class Matrix, class Vector>
struct BdaSolverInfo
{
using WellContribFunc = std::function<void(WellContributions&)>;
using Bridge = BdaBridge<Matrix,Vector,Matrix::block_type::rows>;
BdaSolverInfo(const std::string& accelerator_mode,
const int linear_solver_verbosity,
const int maxit,
const double tolerance,
const int platformID,
const int deviceID,
const bool opencl_ilu_parallel,
const std::string& linsolver);
~BdaSolverInfo();
template<class Grid>
void prepare(const Grid& grid,
const Dune::CartesianIndexMapper<Grid>& cartMapper,
const std::vector<Well>& wellsForConn,
const std::vector<int>& cellPartition,
const size_t nonzeroes,
const bool useWellConn);
bool apply(Vector& rhs,
const bool useWellConn,
WellContribFunc getContribs,
const int rank,
Matrix& matrix,
Vector& x,
Dune::InverseOperatorResult& result);
bool gpuActive();
int numJacobiBlocks_ = 0;
private:
/// Create sparsity pattern for block-Jacobi matrix based on partitioning of grid.
/// Do not initialize the values, that is done in copyMatToBlockJac()
template<class Grid>
void blockJacobiAdjacency(const Grid& grid,
const std::vector<int>& cell_part,
size_t nonzeroes);
void copyMatToBlockJac(const Matrix& mat, Matrix& blockJac);
std::unique_ptr<Bridge> bridge_;
std::string accelerator_mode_;
std::unique_ptr<Matrix> blockJacobiForGPUILU0_;
std::vector<std::set<int>> wellConnectionsGraph_;
};
#endif
#ifdef HAVE_MPI
/// Copy values in parallel.
void copyParValues(std::any& parallelInformation, size_t size,
Dune::OwnerOverlapCopyCommunication<int,int>& comm);
#endif
/// Zero out off-diagonal blocks on rows corresponding to overlap cells
/// Diagonal blocks on ovelap rows are set to diag(1.0).
template<class Matrix>
void makeOverlapRowsInvalid(Matrix& matrix,
const std::vector<int>& overlapRows);
/// Create sparsity pattern for block-Jacobi matrix based on partitioning of grid.
/// Do not initialize the values, that is done in copyMatToBlockJac()
template<class Matrix, class Grid>
std::unique_ptr<Matrix> blockJacobiAdjacency(const Grid& grid,
const std::vector<int>& cell_part,
size_t nonzeroes,
const std::vector<std::set<int>>& wellConnectionsGraph);
}
/// This class solves the fully implicit black-oil system by
/// solving the reduced system (after eliminating well variables)
/// as a block-structured matrix (one block for all cell variables) for a fixed
/// number of cell variables np .
template <class TypeTag>
class ISTLSolverEbos
{
protected:
using GridView = GetPropType<TypeTag, Properties::GridView>;
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
using SparseMatrixAdapter = GetPropType<TypeTag, Properties::SparseMatrixAdapter>;
using Vector = GetPropType<TypeTag, Properties::GlobalEqVector>;
using Indices = GetPropType<TypeTag, Properties::Indices>;
using WellModel = GetPropType<TypeTag, Properties::EclWellModel>;
using Simulator = GetPropType<TypeTag, Properties::Simulator>;
using Matrix = typename SparseMatrixAdapter::IstlMatrix;
using ThreadManager = GetPropType<TypeTag, Properties::ThreadManager>;
using ElementContext = GetPropType<TypeTag, Properties::ElementContext>;
using AbstractSolverType = Dune::InverseOperator<Vector, Vector>;
using AbstractOperatorType = Dune::AssembledLinearOperator<Matrix, Vector, Vector>;
using AbstractPreconditionerType = Dune::PreconditionerWithUpdate<Vector, Vector>;
using WellModelOperator = WellModelAsLinearOperator<WellModel, Vector, Vector>;
2020-11-19 04:13:55 -06:00
using ElementMapper = GetPropType<TypeTag, Properties::ElementMapper>;
constexpr static std::size_t pressureIndex = GetPropType<TypeTag, Properties::Indices>::pressureSwitchIdx;
#if HAVE_MPI
using CommunicationType = Dune::OwnerOverlapCopyCommunication<int,int>;
#else
using CommunicationType = Dune::CollectiveCommunication<int>;
#endif
public:
using AssembledLinearOperatorType = Dune::AssembledLinearOperator< Matrix, Vector, Vector >;
static void registerParameters()
{
FlowLinearSolverParameters::registerParameters<TypeTag>();
}
/// Construct a system solver.
/// \param[in] simulator The opm-models simulator object
/// \param[in] parameters Explicit parameters for solver setup, do not
/// read them from command line parameters.
ISTLSolverEbos(const Simulator& simulator, const FlowLinearSolverParameters& parameters)
: simulator_(simulator),
iterations_( 0 ),
calls_( 0 ),
converged_(false),
matrix_(nullptr),
parameters_(parameters)
{
initialize();
}
/// Construct a system solver.
/// \param[in] simulator The opm-models simulator object
explicit ISTLSolverEbos(const Simulator& simulator)
2018-11-12 04:03:54 -06:00
: simulator_(simulator),
iterations_( 0 ),
2022-04-28 02:24:34 -05:00
calls_( 0 ),
converged_(false),
matrix_(nullptr)
{
parameters_.template init<TypeTag>(simulator_.vanguard().eclState().getSimulationConfig().useCPR());
initialize();
}
void initialize()
{
OPM_TIMEBLOCK(IstlSolverEbos);
prm_ = setupPropertyTree(parameters_,
EWOMS_PARAM_IS_SET(TypeTag, int, LinearSolverMaxIter),
EWOMS_PARAM_IS_SET(TypeTag, double, LinearSolverReduction));
const bool on_io_rank = (simulator_.gridView().comm().rank() == 0);
#if HAVE_MPI
comm_.reset( new CommunicationType( simulator_.vanguard().grid().comm() ) );
#endif
2022-11-17 02:59:49 -06:00
#if COMPILE_BDA_BRIDGE
{
2020-12-22 05:57:01 -06:00
std::string accelerator_mode = EWOMS_GET_PARAM(TypeTag, std::string, AcceleratorMode);
if ((simulator_.vanguard().grid().comm().size() > 1) && (accelerator_mode != "none")) {
if (on_io_rank) {
2022-11-17 02:27:00 -06:00
OpmLog::warning("Cannot use GPU with MPI, GPU are disabled");
}
2020-12-22 05:57:01 -06:00
accelerator_mode = "none";
}
const int platformID = EWOMS_GET_PARAM(TypeTag, int, OpenclPlatformId);
const int deviceID = EWOMS_GET_PARAM(TypeTag, int, BdaDeviceId);
const int maxit = EWOMS_GET_PARAM(TypeTag, int, LinearSolverMaxIter);
const double tolerance = EWOMS_GET_PARAM(TypeTag, double, LinearSolverReduction);
const bool opencl_ilu_parallel = EWOMS_GET_PARAM(TypeTag, bool, OpenclIluParallel);
const int linear_solver_verbosity = parameters_.linear_solver_verbosity_;
std::string linsolver = EWOMS_GET_PARAM(TypeTag, std::string, LinearSolver);
bdaBridge = std::make_unique<detail::BdaSolverInfo<Matrix,Vector>>(accelerator_mode,
linear_solver_verbosity,
maxit,
tolerance,
platformID,
deviceID,
opencl_ilu_parallel,
linsolver);
2020-03-18 07:53:40 -05:00
}
#else
2020-12-22 05:57:01 -06:00
if (EWOMS_GET_PARAM(TypeTag, std::string, AcceleratorMode) != "none") {
2022-11-17 02:27:00 -06:00
OPM_THROW(std::logic_error,"Cannot use accelerated solver since CUDA, OpenCL and amgcl were not found by cmake");
}
#endif
2018-11-12 04:03:54 -06:00
extractParallelGridInformationToISTL(simulator_.vanguard().grid(), parallelInformation_);
// For some reason simulator_.model().elementMapper() is not initialized at this stage
2021-12-01 07:00:21 -06:00
//const auto& elemMapper = simulator_.model().elementMapper(); //does not work.
// Set it up manually
2020-11-19 04:13:55 -06:00
ElementMapper elemMapper(simulator_.vanguard().gridView(), Dune::mcmgElementLayout());
detail::findOverlapAndInterior(simulator_.vanguard().grid(), elemMapper, overlapRows_, interiorRows_);
useWellConn_ = EWOMS_GET_PARAM(TypeTag, bool, MatrixAddWellContributions);
const bool ownersFirst = EWOMS_GET_PARAM(TypeTag, bool, OwnerCellsFirst);
if (!ownersFirst) {
const std::string msg = "The linear solver no longer supports --owner-cells-first=false.";
if (on_io_rank) {
OpmLog::error(msg);
}
OPM_THROW_NOLOG(std::runtime_error, msg);
}
flexibleSolver_.interiorCellNum_ = detail::numMatrixRowsToUseInSolver(simulator_.vanguard().grid(), true);
// Print parameters to PRT/DBG logs.
if (on_io_rank && parameters_.linear_solver_print_json_definition_) {
std::ostringstream os;
os << "Property tree for linear solver:\n";
prm_.write_json(os, true);
OpmLog::note(os.str());
}
2018-11-12 04:03:54 -06:00
}
// nothing to clean here
void eraseMatrix()
{
2018-11-12 04:03:54 -06:00
}
void prepare(const SparseMatrixAdapter& M, Vector& b)
{
prepare(M.istlMatrix(), b);
}
void prepare(const Matrix& M, Vector& b)
{
OPM_TIMEBLOCK(istlSolverEbosPrepare);
const bool firstcall = (matrix_ == nullptr);
#if HAVE_MPI
if (firstcall) {
const size_t size = M.N();
detail::copyParValues(parallelInformation_, size, *comm_);
}
#endif
// update matrix entries for solvers.
if (firstcall) {
// ebos will not change the matrix object. Hence simply store a pointer
// to the original one with a deleter that does nothing.
// Outch! We need to be able to scale the linear system! Hence const_cast
matrix_ = const_cast<Matrix*>(&M);
useWellConn_ = EWOMS_GET_PARAM(TypeTag, bool, MatrixAddWellContributions);
// setup sparsity pattern for jacobi matrix for preconditioner (only used for openclSolver)
#if HAVE_OPENCL
bdaBridge->numJacobiBlocks_ = EWOMS_GET_PARAM(TypeTag, int, NumJacobiBlocks);
bdaBridge->prepare(simulator_.vanguard().grid(),
simulator_.vanguard().cartesianIndexMapper(),
simulator_.vanguard().schedule().getWellsatEnd(),
simulator_.vanguard().cellPartition(),
getMatrix().nonzeroes(), useWellConn_);
#endif
} else {
// Pointers should not change
if ( &M != matrix_ ) {
2023-01-02 08:17:49 -06:00
OPM_THROW(std::logic_error,
"Matrix objects are expected to be reused when reassembling!");
}
}
rhs_ = &b;
if (isParallel() && prm_.get<std::string>("preconditioner.type") != "ParOverILU0") {
detail::makeOverlapRowsInvalid(getMatrix(), overlapRows_);
}
#if COMPILE_BDA_BRIDGE
if(!bdaBridge->gpuActive()){
prepareFlexibleSolver();
}
#else
prepareFlexibleSolver();
#endif
}
void setResidual(Vector& /* b */)
{
// rhs_ = &b; // Must be handled in prepare() instead.
2018-11-12 04:03:54 -06:00
}
void getResidual(Vector& b) const
{
b = *rhs_;
}
void setMatrix(const SparseMatrixAdapter& /* M */)
{
// matrix_ = &M.istlMatrix(); // Must be handled in prepare() instead.
}
bool solve(Vector& x)
{
OPM_TIMEBLOCK(istlSolverEbosSolve);
2022-04-28 02:24:34 -05:00
calls_ += 1;
// Write linear system if asked for.
const int verbosity = prm_.get<int>("verbosity", 0);
const bool write_matrix = verbosity > 10;
if (write_matrix) {
Helper::writeSystem(simulator_, //simulator is only used to get names
getMatrix(),
*rhs_,
comm_.get());
}
// Solve system.
Dune::InverseOperatorResult result;
2018-11-12 04:03:54 -06:00
2022-11-17 02:59:49 -06:00
#if COMPILE_BDA_BRIDGE
std::function<void(WellContributions&)> getContribs =
[this](WellContributions& w)
{
this->simulator_.problem().wellModel().getWellContributions(w);
};
if (!bdaBridge->apply(*rhs_, useWellConn_, getContribs,
simulator_.gridView().comm().rank(),
const_cast<Matrix&>(this->getMatrix()),
x, result))
#endif
{
OPM_TIMEBLOCK(flexibleSolverApply);
#if COMPILE_BDA_BRIDGE
if(bdaBridge->gpuActive()){
prepareFlexibleSolver();
}
#endif
assert(flexibleSolver_.solver_);
flexibleSolver_.solver_->apply(x, *rhs_, result);
2019-03-14 03:04:51 -05:00
}
2018-11-12 04:03:54 -06:00
// Check convergence, iterations etc.
checkConvergence(result);
2018-11-12 04:03:54 -06:00
return converged_;
}
/// Solve the system of linear equations Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] residual residual object containing A and b.
/// \return the solution x
/// \copydoc NewtonIterationBlackoilInterface::iterations
int iterations () const { return iterations_; }
/// \copydoc NewtonIterationBlackoilInterface::parallelInformation
const std::any& parallelInformation() const { return parallelInformation_; }
2018-11-12 04:03:54 -06:00
protected:
#if HAVE_MPI
using Comm = Dune::OwnerOverlapCopyCommunication<int, int>;
#endif
void checkConvergence( const Dune::InverseOperatorResult& result ) const
{
// store number of iterations
iterations_ = result.iterations;
2018-11-12 04:03:54 -06:00
converged_ = result.converged;
if(!converged_){
if(result.reduction < parameters_.relaxed_linear_solver_reduction_){
std::stringstream ss;
ss<< "Full linear solver tolerance not achived. The reduction is:" << result.reduction
<< " after " << result.iterations << " iterations ";
OpmLog::warning(ss.str());
converged_ = true;
}
}
// Check for failure of linear solver.
if (!parameters_.ignoreConvergenceFailure_ && !converged_) {
const std::string msg("Convergence failure for linear solver.");
2022-12-13 05:54:27 -06:00
OPM_THROW_NOLOG(NumericalProblem, msg);
}
}
protected:
2018-11-12 04:03:54 -06:00
bool isParallel() const {
#if HAVE_MPI
return comm_->communicator().size() > 1;
2018-11-12 04:03:54 -06:00
#else
return false;
#endif
}
void prepareFlexibleSolver()
{
2023-02-28 06:58:02 -06:00
OPM_TIMEBLOCK(flexibleSolverPrepare);
if (shouldCreateSolver()) {
std::function<Vector()> trueFunc =
[this]
{
return this->getTrueImpesWeights(pressureIndex);
};
if (!useWellConn_) {
auto wellOp = std::make_unique<WellModelOperator>(simulator_.problem().wellModel());
flexibleSolver_.wellOperator_ = std::move(wellOp);
}
OPM_TIMEBLOCK(flexibleSolverCreate);
flexibleSolver_.create(getMatrix(),
isParallel(),
prm_,
pressureIndex,
trueFunc,
*comm_);
}
else
{
OPM_TIMEBLOCK(flexibleSolverUpdate);
flexibleSolver_.pre_->update();
}
}
/// Return true if we should (re)create the whole solver,
/// instead of just calling update() on the preconditioner.
bool shouldCreateSolver() const
{
// Decide if we should recreate the solver or just do
// a minimal preconditioner update.
if (!flexibleSolver_.solver_) {
return true;
}
if (this->parameters_.cpr_reuse_setup_ == 0) {
// Always recreate solver.
2020-10-11 01:20:19 -05:00
return true;
}
if (this->parameters_.cpr_reuse_setup_ == 1) {
// Recreate solver on the first iteration of every timestep.
2020-10-11 01:20:19 -05:00
const int newton_iteration = this->simulator_.model().newtonMethod().numIterations();
2020-10-11 15:46:02 -05:00
return newton_iteration == 0;
2020-10-11 01:20:19 -05:00
}
if (this->parameters_.cpr_reuse_setup_ == 2) {
// Recreate solver if the last solve used more than 10 iterations.
2020-10-11 15:46:02 -05:00
return this->iterations() > 10;
2018-11-12 04:03:54 -06:00
}
2022-04-28 02:24:34 -05:00
if (this->parameters_.cpr_reuse_setup_ == 3) {
// Recreate solver if the last solve used more than 10 iterations.
return false;
}
2022-05-10 03:16:18 -05:00
if (this->parameters_.cpr_reuse_setup_ == 4) {
2022-06-10 08:59:45 -05:00
// Recreate solver every 'step' solve calls.
const int step = this->parameters_.cpr_reuse_interval_;
const bool create = ((calls_ % step) == 0);
2022-04-28 02:24:34 -05:00
return create;
}
2020-10-11 01:20:19 -05:00
2022-06-10 08:59:45 -05:00
// If here, we have an invalid parameter.
const bool on_io_rank = (simulator_.gridView().comm().rank() == 0);
std::string msg = "Invalid value: " + std::to_string(this->parameters_.cpr_reuse_setup_)
+ " for --cpr-reuse-setup parameter, run with --help to see allowed values.";
if (on_io_rank) {
OpmLog::error(msg);
}
throw std::runtime_error(msg);
2020-10-11 01:20:19 -05:00
2022-06-10 08:59:45 -05:00
// Never reached.
2020-10-11 01:20:19 -05:00
return false;
}
// Weights to make approximate pressure equations.
2019-03-20 04:24:44 -05:00
// Calculated from the storage terms (only) of the
// conservation equations, ignoring all other terms.
Vector getTrueImpesWeights(int pressureVarIndex) const
{
OPM_TIMEBLOCK(getTrueImpesWeights);
Vector weights(rhs_->size());
ElementContext elemCtx(simulator_);
Amg::getTrueImpesWeights(pressureVarIndex, weights,
simulator_.vanguard().gridView(),
elemCtx, simulator_.model(),
ThreadManager::threadId());
return weights;
}
2019-03-12 07:55:11 -05:00
Matrix& getMatrix()
{
return *matrix_;
}
const Matrix& getMatrix() const
{
return *matrix_;
}
2018-11-12 04:03:54 -06:00
const Simulator& simulator_;
mutable int iterations_;
2022-04-28 02:24:34 -05:00
mutable int calls_;
2018-11-12 04:03:54 -06:00
mutable bool converged_;
std::any parallelInformation_;
2019-03-12 07:55:11 -05:00
// non-const to be able to scale the linear system
Matrix* matrix_;
2018-11-12 04:03:54 -06:00
Vector *rhs_;
2022-11-17 02:59:49 -06:00
#if COMPILE_BDA_BRIDGE
std::unique_ptr<detail::BdaSolverInfo<Matrix, Vector>> bdaBridge;
#endif
detail::FlexibleSolverInfo<Matrix,Vector,CommunicationType> flexibleSolver_;
std::vector<int> overlapRows_;
std::vector<int> interiorRows_;
bool useWellConn_;
FlowLinearSolverParameters parameters_;
PropertyTree prm_;
std::shared_ptr< CommunicationType > comm_;
}; // end ISTLSolver
} // namespace Opm
#endif