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

585 lines
23 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>
2023-07-04 08:25:11 -05:00
#include <dune/istl/solver.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
{
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,
2023-08-15 02:32:10 -05:00
std::size_t pressureIndex,
std::function<Vector()> trueFunc,
const bool forceSerial,
Comm& comm);
std::unique_ptr<AbstractSolverType> solver_;
std::unique_ptr<AbstractOperatorType> op_;
std::unique_ptr<LinearOperatorExtra<Vector,Vector>> wellOperator_;
AbstractPreconditionerType* pre_ = nullptr;
2023-08-15 02:32:10 -05:00
std::size_t interiorCellNum_ = 0;
};
#ifdef HAVE_MPI
/// Copy values in parallel.
2023-08-15 02:32:10 -05:00
void copyParValues(std::any& parallelInformation, std::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,
2023-08-15 02:32:10 -05:00
std::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.
2023-09-01 04:19:36 -05:00
/// \param[in] forceSerial If true, will set up a serial linear solver only,
/// local to the current rank, instead of creating a
/// parallel (MPI distributed) linear solver.
ISTLSolverEbos(const Simulator& simulator, const FlowLinearSolverParameters& parameters, bool forceSerial = false)
: simulator_(simulator),
iterations_( 0 ),
converged_(false),
matrix_(nullptr),
2022-11-09 09:54:07 -06:00
parameters_{parameters},
forceSerial_(forceSerial)
{
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-11-09 09:54:07 -06:00
solveCount_(0),
converged_(false),
matrix_(nullptr)
{
2022-11-09 09:54:07 -06:00
parameters_.resize(1);
parameters_[0].template init<TypeTag>(simulator_.vanguard().eclState().getSimulationConfig().useCPR());
initialize();
}
void initialize()
{
OPM_TIMEBLOCK(IstlSolverEbos);
if (parameters_[0].linsolver_ == "hybrid") {
// Experimental hybrid configuration.
// When chosen, will set up two solvers, one with CPRW
// and the other with ILU0 preconditioner. More general
// options may be added later.
prm_.clear();
parameters_.clear();
{
FlowLinearSolverParameters para;
para.init<TypeTag>(false);
para.linsolver_ = "cprw";
parameters_.push_back(para);
prm_.push_back(setupPropertyTree(parameters_[0],
EWOMS_PARAM_IS_SET(TypeTag, int, LinearSolverMaxIter),
EWOMS_PARAM_IS_SET(TypeTag, double, LinearSolverReduction)));
}
{
FlowLinearSolverParameters para;
para.init<TypeTag>(false);
para.linsolver_ = "ilu0";
parameters_.push_back(para);
prm_.push_back(setupPropertyTree(parameters_[1],
EWOMS_PARAM_IS_SET(TypeTag, int, LinearSolverMaxIter),
EWOMS_PARAM_IS_SET(TypeTag, double, LinearSolverReduction)));
}
// ------------
} else {
// Do a normal linear solver setup.
assert(parameters_.size() == 1);
assert(prm_.empty());
2022-11-09 09:54:07 -06:00
prm_.push_back(setupPropertyTree(parameters_[0],
EWOMS_PARAM_IS_SET(TypeTag, int, LinearSolverMaxIter),
EWOMS_PARAM_IS_SET(TypeTag, double, LinearSolverReduction)));
2022-11-09 09:54:07 -06:00
}
flexibleSolver_.resize(prm_.size());
const bool on_io_rank = (simulator_.gridView().comm().rank() == 0);
#if HAVE_MPI
comm_.reset( new CommunicationType( simulator_.vanguard().grid().comm() ) );
#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);
}
2022-11-09 09:54:07 -06:00
const int interiorCellNum_ = detail::numMatrixRowsToUseInSolver(simulator_.vanguard().grid(), true);
for (auto& f : flexibleSolver_) {
f.interiorCellNum_ = interiorCellNum_;
}
// Print parameters to PRT/DBG logs.
2022-11-09 09:54:07 -06:00
if (on_io_rank && parameters_[activeSolverNum_].linear_solver_print_json_definition_) {
std::ostringstream os;
2022-11-09 09:54:07 -06:00
os << "Property tree for linear solvers:\n";
for (std::size_t i = 0; i<prm_.size(); i++) {
prm_[i].write_json(os, true);
std::cerr<< "debug: ["<<i<<"] : " << os.str() <<std::endl;
}
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
}
2022-11-09 09:54:07 -06:00
void setActiveSolver(const int num)
{
if (num > static_cast<int>(prm_.size()) - 1) {
OPM_THROW(std::logic_error, "Solver number " + std::to_string(num) + " not available.");
2022-11-09 09:54:07 -06:00
}
activeSolverNum_ = num;
auto cc = Dune::MPIHelper::getCollectiveCommunication();
if (cc.rank() == 0) {
OpmLog::debug("Active solver = " + std::to_string(activeSolverNum_)
+ " (" + parameters_[activeSolverNum_].linsolver_ + ")");
2022-11-09 09:54:07 -06:00
}
}
int numAvailableSolvers()
{
return flexibleSolver_.size();
2022-11-09 09:54:07 -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
2023-09-01 01:48:11 -05:00
if (firstcall && isParallel()) {
2023-08-15 02:32:10 -05:00
const std::size_t size = M.N();
2023-09-01 01:48:11 -05:00
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)
} 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;
2022-11-09 09:54:07 -06:00
// TODO: check all solvers, not just one.
if (isParallel() && prm_[activeSolverNum_].template get<std::string>("preconditioner.type") != "ParOverILU0") {
detail::makeOverlapRowsInvalid(getMatrix(), overlapRows_);
}
prepareFlexibleSolver();
}
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.
}
2022-11-09 09:54:07 -06:00
int getSolveCount() const {
return solveCount_;
}
void resetSolveCount() {
solveCount_ = 0;
}
bool solve(Vector& x)
{
OPM_TIMEBLOCK(istlSolverEbosSolve);
2022-11-09 09:54:07 -06:00
++solveCount_;
// Write linear system if asked for.
2022-11-09 09:54:07 -06:00
const int verbosity = prm_[activeSolverNum_].get("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;
{
OPM_TIMEBLOCK(flexibleSolverApply);
2022-11-09 09:54:07 -06:00
assert(flexibleSolver_[activeSolverNum_].solver_);
flexibleSolver_[activeSolverNum_].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_; }
const CommunicationType* comm() const { return comm_.get(); }
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_){
2022-11-09 09:54:07 -06:00
if(result.reduction < parameters_[activeSolverNum_].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.
2022-11-09 09:54:07 -06:00
if (!parameters_[activeSolverNum_].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 !forceSerial_ && 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());
2022-11-09 09:54:07 -06:00
flexibleSolver_[activeSolverNum_].wellOperator_ = std::move(wellOp);
}
OPM_TIMEBLOCK(flexibleSolverCreate);
2022-11-09 09:54:07 -06:00
flexibleSolver_[activeSolverNum_].create(getMatrix(),
isParallel(),
prm_[activeSolverNum_],
pressureIndex,
trueFunc,
forceSerial_,
*comm_);
}
else
{
OPM_TIMEBLOCK(flexibleSolverUpdate);
2022-11-09 09:54:07 -06:00
flexibleSolver_[activeSolverNum_].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.
2022-11-09 09:54:07 -06:00
if (flexibleSolver_.empty()) {
return true;
}
if (!flexibleSolver_[activeSolverNum_].solver_) {
return true;
}
2022-11-09 09:54:07 -06:00
if (this->parameters_[activeSolverNum_].cpr_reuse_setup_ == 0) {
// Always recreate solver.
2020-10-11 01:20:19 -05:00
return true;
}
2022-11-09 09:54:07 -06:00
if (this->parameters_[activeSolverNum_].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
}
2022-11-09 09:54:07 -06:00
if (this->parameters_[activeSolverNum_].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-11-09 09:54:07 -06:00
if (this->parameters_[activeSolverNum_].cpr_reuse_setup_ == 3) {
2022-04-28 02:24:34 -05:00
// Recreate solver if the last solve used more than 10 iterations.
return false;
}
2022-11-09 09:54:07 -06:00
if (this->parameters_[activeSolverNum_].cpr_reuse_setup_ == 4) {
2022-06-10 08:59:45 -05:00
// Recreate solver every 'step' solve calls.
2022-11-09 09:54:07 -06:00
const int step = this->parameters_[activeSolverNum_].cpr_reuse_interval_;
const bool create = ((solveCount_ % 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);
2022-11-09 09:54:07 -06:00
std::string msg = "Invalid value: " + std::to_string(this->parameters_[activeSolverNum_].cpr_reuse_setup_)
2022-06-10 08:59:45 -05:00
+ " 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-11-09 09:54:07 -06:00
mutable int solveCount_;
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-09 09:54:07 -06:00
int activeSolverNum_ = 0;
std::vector<detail::FlexibleSolverInfo<Matrix,Vector,CommunicationType>> flexibleSolver_;
std::vector<int> overlapRows_;
std::vector<int> interiorRows_;
bool useWellConn_;
2022-11-09 09:54:07 -06:00
std::vector<FlowLinearSolverParameters> parameters_;
bool forceSerial_ = false;
2022-11-09 09:54:07 -06:00
std::vector<PropertyTree> prm_;
std::shared_ptr< CommunicationType > comm_;
}; // end ISTLSolver
} // namespace Opm
#endif