1529 lines
53 KiB
C++
1529 lines
53 KiB
C++
// Copyright (c) 2010-2025, 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.
|
|
|
|
#ifndef MFEM_SOLVERS
|
|
#define MFEM_SOLVERS
|
|
|
|
#include "../config/config.hpp"
|
|
#include "densemat.hpp"
|
|
#include "handle.hpp"
|
|
#include <memory>
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
#include <mpi.h>
|
|
#endif
|
|
|
|
#ifdef MFEM_USE_SUITESPARSE
|
|
#include "sparsemat.hpp"
|
|
#include <umfpack.h>
|
|
#include <klu.h>
|
|
#endif
|
|
|
|
namespace mfem
|
|
{
|
|
|
|
class BilinearForm;
|
|
|
|
/// Abstract base class for an iterative solver controller
|
|
class IterativeSolverController
|
|
{
|
|
protected:
|
|
/// The last IterativeSolver to which this controller was attached.
|
|
const class IterativeSolver *iter_solver;
|
|
|
|
/// In MonitorResidual or MonitorSolution, this member variable can be set
|
|
/// to true to indicate early convergence.
|
|
bool converged = false;
|
|
|
|
public:
|
|
IterativeSolverController() : iter_solver(nullptr) {}
|
|
|
|
virtual ~IterativeSolverController() {}
|
|
|
|
/// Has the solver converged?
|
|
///
|
|
/// Can be used if convergence is detected in the controller (before reaching
|
|
/// the relative or absolute tolerance of the IterativeSolver).
|
|
bool HasConverged() { return converged; }
|
|
|
|
/// Reset the controller to its initial state.
|
|
///
|
|
/// This function is called by the IterativeSolver::Mult()
|
|
/// method on the first iteration.
|
|
virtual void Reset() { converged = false; }
|
|
|
|
/// Monitor the solution vector r
|
|
virtual void MonitorResidual(int it, real_t norm, const Vector &r,
|
|
bool final)
|
|
{
|
|
}
|
|
|
|
/// Monitor the solution vector x
|
|
virtual void MonitorSolution(int it, real_t norm, const Vector &x,
|
|
bool final)
|
|
{
|
|
}
|
|
|
|
/// Indicates if the controller requires an updated solution every iteration
|
|
/** The default behavior is to not require the updated solution to allow
|
|
solvers to skip it if not needed otherwise. */
|
|
virtual bool RequiresUpdatedSolution() const { return false; }
|
|
|
|
/** @brief This method is invoked by IterativeSolver::SetController(),
|
|
informing the controller which IterativeSolver is using it. */
|
|
void SetIterativeSolver(const IterativeSolver &solver)
|
|
{ iter_solver = &solver; }
|
|
};
|
|
|
|
/// Keeping the alias for backward compatibility
|
|
using IterativeSolverMonitor = IterativeSolverController;
|
|
|
|
/// Abstract base class for iterative solver
|
|
class IterativeSolver : public Solver
|
|
{
|
|
public:
|
|
/** @brief Settings for the output behavior of the IterativeSolver.
|
|
|
|
By default, all output is suppressed. The construction of the desired
|
|
print level can be achieved through a builder pattern, for example
|
|
|
|
PrintLevel().Errors().Warnings()
|
|
|
|
constructs the print level with only errors and warnings enabled.
|
|
*/
|
|
struct PrintLevel
|
|
{
|
|
/** @brief If a fatal problem has been detected the failure will be
|
|
reported to @ref mfem::err. */
|
|
bool errors = false;
|
|
/** @brief If a non-fatal problem has been detected some context-specific
|
|
information will be reported to @ref mfem::out */
|
|
bool warnings = false;
|
|
/** @brief Detailed information about each iteration will be reported to
|
|
@ref mfem::out */
|
|
bool iterations = false;
|
|
/** @brief A summary of the solver process will be reported after the last
|
|
iteration to @ref mfem::out */
|
|
bool summary = false;
|
|
/** @brief Information about the first and last iteration will be printed
|
|
to @ref mfem::out */
|
|
bool first_and_last = false;
|
|
|
|
/// Initializes the print level to suppress
|
|
PrintLevel() = default;
|
|
|
|
/** @name Builder
|
|
These methods are utilized to construct PrintLevel objects through a
|
|
builder approach by chaining the function calls in this group. */
|
|
///@{
|
|
PrintLevel &None() { *this = PrintLevel(); return *this; }
|
|
PrintLevel &Warnings() { warnings=true; return *this; }
|
|
PrintLevel &Errors() { errors=true; return *this; }
|
|
PrintLevel &Iterations() { iterations=true; return *this; }
|
|
PrintLevel &FirstAndLast() { first_and_last=true; return *this; }
|
|
PrintLevel &Summary() { summary=true; return *this; }
|
|
PrintLevel &All()
|
|
{ return Warnings().Errors().Iterations().FirstAndLast().Summary(); }
|
|
///@}
|
|
};
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
private:
|
|
int dot_prod_type; // 0 - local, 1 - global over 'comm'
|
|
MPI_Comm comm = MPI_COMM_NULL;
|
|
#endif
|
|
|
|
protected:
|
|
const Operator *oper;
|
|
Solver *prec;
|
|
IterativeSolverController *controller = nullptr;
|
|
InnerProductOperator *dot_oper = nullptr;
|
|
|
|
/// @name Reporting (protected attributes and member functions)
|
|
///@{
|
|
|
|
/** @brief (DEPRECATED) Legacy print level definition, which is left for
|
|
compatibility with custom iterative solvers.
|
|
@deprecated #print_options should be used instead. */
|
|
int print_level = -1;
|
|
|
|
/** @brief Output behavior for the iterative solver.
|
|
|
|
This primarily controls the output behavior of the iterative solvers
|
|
provided by this library. This member must be synchronized with
|
|
#print_level to ensure compatibility with custom iterative solvers. */
|
|
PrintLevel print_options;
|
|
|
|
/// Convert a legacy print level integer to a PrintLevel object
|
|
PrintLevel FromLegacyPrintLevel(int);
|
|
|
|
/// @brief Use some heuristics to guess a legacy print level corresponding to
|
|
/// the given PrintLevel.
|
|
static int GuessLegacyPrintLevel(PrintLevel);
|
|
///@}
|
|
|
|
/// @name Convergence (protected attributes)
|
|
///@{
|
|
|
|
/// Limit for the number of iterations the solver is allowed to do
|
|
int max_iter;
|
|
|
|
/// Relative tolerance.
|
|
real_t rel_tol;
|
|
|
|
/// Absolute tolerance.
|
|
real_t abs_tol;
|
|
|
|
///@}
|
|
|
|
/// @name Solver statistics (protected attributes)
|
|
/// Every IterativeSolver is expected to define these in its Mult() call.
|
|
///@{
|
|
|
|
mutable int final_iter = -1;
|
|
mutable bool converged = false;
|
|
mutable real_t initial_norm = -1.0, final_norm = -1.0;
|
|
|
|
///@}
|
|
|
|
|
|
/** @brief Return the standard (l2, i.e., Euclidean) inner product of
|
|
@a x and @a y
|
|
@details Overriding this method in a derived class enables a
|
|
custom inner product.
|
|
*/
|
|
virtual real_t Dot(const Vector &x, const Vector &y) const;
|
|
|
|
/// Return the inner product norm of @a x, using the inner product defined by Dot()
|
|
real_t Norm(const Vector &x) const { return sqrt(Dot(x, x)); }
|
|
|
|
/// Indicated if the controller requires an update of the solution
|
|
bool ControllerRequiresUpdate() const { return controller && controller->RequiresUpdatedSolution(); }
|
|
|
|
/// Monitor both the residual @a r and the solution @a x
|
|
bool Monitor(int it, real_t norm, const Vector& r, const Vector& x,
|
|
bool final=false) const;
|
|
|
|
public:
|
|
IterativeSolver();
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
IterativeSolver(MPI_Comm comm_);
|
|
#endif
|
|
|
|
/** @name Convergence
|
|
@brief Termination criteria for the iterative solvers.
|
|
|
|
@details While the convergence criterion is solver specific, most of the
|
|
provided iterative solvers use one of the following criteria
|
|
|
|
$ ||r||_X \leq tol_{rel}||r_0||_X $,
|
|
|
|
$ ||r||_X \leq tol_{abs} $,
|
|
|
|
$ ||r||_X \leq \max\{ tol_{abs}, tol_{rel} ||r_0||_X \} $,
|
|
|
|
where X denotes the space in which the norm is measured. The choice of
|
|
X depends on the specific iterative solver.
|
|
*/
|
|
///@{
|
|
void SetRelTol(real_t rtol) { rel_tol = rtol; }
|
|
void SetAbsTol(real_t atol) { abs_tol = atol; }
|
|
void SetMaxIter(int max_it) { max_iter = max_it; }
|
|
///@}
|
|
|
|
/** @name Reporting
|
|
These options control the internal reporting behavior into ::mfem::out
|
|
and ::mfem::err of the iterative solvers.
|
|
*/
|
|
///@{
|
|
|
|
/// @brief Legacy method to set the level of verbosity of the solver output.
|
|
/** This is the old way to control what information will be printed to
|
|
::mfem::out and ::mfem::err. The behavior for the print level for all
|
|
iterative solvers is:
|
|
|
|
- -1: Suppress all outputs.
|
|
- 0: Print information about all detected issues (e.g. no convergence).
|
|
- 1: Same as level 0, but with detailed information about each
|
|
iteration.
|
|
- 2: Print detected issues and a summary when the solver terminates.
|
|
- 3: Same as 2, but print also the first and last iterations.
|
|
- >3: Custom print options which are dependent on the specific solver.
|
|
|
|
In parallel, only rank 0 produces output.
|
|
|
|
@note It is recommended to use @ref SetPrintLevel(PrintLevel) instead.
|
|
|
|
@note Some derived classes, like KINSolver, redefine this method and use
|
|
their own set of print level constants. */
|
|
virtual void SetPrintLevel(int print_lvl);
|
|
|
|
/// @brief Set the level of verbosity of the solver output.
|
|
/** In parallel, only rank 0 produces outputs. Errors are output to
|
|
::mfem::err and all other information to ::mfem::out.
|
|
|
|
@note Not all subclasses of IterativeSolver support all possible options.
|
|
|
|
@note Some derived classes, like KINSolver, disable this method in favor
|
|
of SetPrintLevel(int).
|
|
|
|
@sa PrintLevel for possible options.
|
|
*/
|
|
virtual void SetPrintLevel(PrintLevel);
|
|
///@}
|
|
|
|
/// @name Solver statistics.
|
|
/// These are valid after the call to Mult().
|
|
///@{
|
|
|
|
/// Returns the number of iterations taken during the last call to Mult()
|
|
int GetNumIterations() const { return final_iter; }
|
|
/// Returns true if the last call to Mult() converged successfully.
|
|
bool GetConverged() const { return converged; }
|
|
/// @brief Returns the initial residual norm from the last call to Mult().
|
|
///
|
|
/// This function returns the norm of the residual (or preconditioned
|
|
/// residual, depending on the solver), computed before the start of the
|
|
/// iteration.
|
|
real_t GetInitialNorm() const { return initial_norm; }
|
|
/// @brief Returns the final residual norm after termination of the solver
|
|
/// during the last call to Mult().
|
|
///
|
|
/// This function returns the norm of the residual (or preconditioned
|
|
/// residual, depending on the solver), corresponding to the returned
|
|
/// solution.
|
|
real_t GetFinalNorm() const { return final_norm; }
|
|
/// @brief Returns the final residual norm after termination of the solver
|
|
/// during the last call to Mult(), divided by the initial residual norm.
|
|
/// Returns -1 if one of these norms is left undefined by the solver.
|
|
///
|
|
/// @sa GetFinalNorm(), GetInitialNorm()
|
|
real_t GetFinalRelNorm() const
|
|
{
|
|
if (final_norm < 0.0 || initial_norm < 0.0) { return -1.0; }
|
|
return final_norm / initial_norm;
|
|
}
|
|
|
|
///@}
|
|
|
|
/// This should be called before SetOperator
|
|
virtual void SetPreconditioner(Solver &pr);
|
|
|
|
/// Also calls SetOperator for the preconditioner if there is one
|
|
void SetOperator(const Operator &op) override;
|
|
|
|
/// Set the iterative solver controller
|
|
void SetController(IterativeSolverController &c)
|
|
{ controller = &c; c.SetIterativeSolver(*this); }
|
|
|
|
/// An alias of SetController() for backward compatibility
|
|
void SetMonitor(IterativeSolverMonitor &m) { SetController(m); }
|
|
|
|
/// Set a user-defined inner product operator (not owned)
|
|
void SetInnerProduct(InnerProductOperator *ipo) { dot_oper = ipo; }
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
/** @brief Return the associated MPI communicator, or MPI_COMM_NULL if no
|
|
communicator is set. */
|
|
MPI_Comm GetComm() const
|
|
{ return dot_prod_type == 0 ? MPI_COMM_NULL : comm; }
|
|
#endif
|
|
};
|
|
|
|
|
|
/** @brief Inner product operator constrained to a list of indices/dofs.
|
|
The method Eval() computes the inner product of two vectors
|
|
only on the constrained entries specified in the constraint list. */
|
|
class ConstrainedInnerProduct : public InnerProductOperator
|
|
{
|
|
protected:
|
|
Array<int> constraint_list; /// List of constrained indices
|
|
mutable Vector xr, yr; /// Restricted vectors
|
|
|
|
public:
|
|
#ifdef MFEM_USE_MPI
|
|
/// @brief Constructor from MPI communicator and a list of constraint indices.
|
|
ConstrainedInnerProduct(MPI_Comm comm_, const Array<int> &list)
|
|
: InnerProductOperator(comm_) { SetIndices(list); }
|
|
/// @brief Constructor from MPI communicator.
|
|
ConstrainedInnerProduct(MPI_Comm comm_) : InnerProductOperator(comm_) {}
|
|
#endif
|
|
|
|
/** @brief Constructor from a list of constraint indices/dofs.
|
|
Specify a @a list of indices to constrain, i.e. each entry
|
|
@a list[i] represents an element of the inner product. */
|
|
ConstrainedInnerProduct(const Array<int> &list) : InnerProductOperator()
|
|
{ SetIndices(list); }
|
|
|
|
/// @brief Set/update the list of constraint indices.
|
|
void SetIndices(const Array<int> &list);
|
|
|
|
/** @brief Compute the inner product (x,y) of vectors x and y,
|
|
only on the constrained entries. */
|
|
virtual real_t Eval(const Vector &x, const Vector &y) override;
|
|
|
|
/// @brief Apply the constraint to vector @a x and return in @a y.
|
|
virtual void Mult(const Vector &x, Vector &y) const override;
|
|
};
|
|
|
|
/** @brief Inner product weighted by operators, @a X and @a Y.
|
|
The method Eval() computes the inner product of two vectors,
|
|
@a x and @a y, weighted by the operators, @a X and @a Y,
|
|
as (Y(y),X(x)). */
|
|
class WeightedInnerProduct : public InnerProductOperator
|
|
{
|
|
protected:
|
|
Operator *operX = nullptr; /// Weighting operator for x
|
|
Operator *operY = nullptr; /// Weighting operator for y
|
|
mutable Vector wx, wy; /// Weighted vectors
|
|
MemoryClass mem_class;
|
|
|
|
public:
|
|
#ifdef MFEM_USE_MPI
|
|
/// @brief Constructor from MPI communicator.
|
|
WeightedInnerProduct(MPI_Comm comm_) : InnerProductOperator(comm_) {}
|
|
#endif
|
|
/// @brief Constructor from weighting operators.
|
|
WeightedInnerProduct(Operator *X, Operator *Y)
|
|
: InnerProductOperator() { SetOperator(X, Y); }
|
|
|
|
/** @brief Set/update the weighting operators (not owned) for each term. */
|
|
void SetOperator(Operator *X, Operator *Y);
|
|
|
|
/// @brief Set/update the same weighting operator (not owned) for both terms.
|
|
void SetOperator(Operator *XY) { SetOperator(XY, XY); }
|
|
|
|
/** @brief Compute the inner product (Y(y),X(x)) of vectors x and y,
|
|
weighted by the operators @a X and @a Y.
|
|
@note Either (but not both) operator, @a X or @a Y, can be set as null, in
|
|
which case the corresponding vector is unmodified (i.e. the operator
|
|
acts as an identity operator). */
|
|
virtual real_t Eval(const Vector &x, const Vector &y) override;
|
|
|
|
/// @brief Apply the weighting operator to vector @a x and return in @a y=Y(X(x)).
|
|
virtual void Mult(const Vector &x, Vector &y) const override;
|
|
};
|
|
|
|
|
|
/// Jacobi smoothing for a given bilinear form (no matrix necessary).
|
|
/** Useful with tensorized, partially assembled operators. Can also be defined
|
|
by given diagonal vector. This is basic Jacobi iteration; for tolerances,
|
|
iteration control, etc. wrap with SLISolver. */
|
|
class OperatorJacobiSmoother : public Solver
|
|
{
|
|
public:
|
|
/** @brief Default constructor: the diagonal will be computed by subsequent
|
|
calls to SetOperator() using the Operator method AssembleDiagonal. */
|
|
/** In this case the array of essential tdofs will be empty. */
|
|
OperatorJacobiSmoother(const real_t damping=1.0);
|
|
|
|
/** Setup a Jacobi smoother with the diagonal of @a a obtained by calling
|
|
a.AssembleDiagonal(). It is assumed that the underlying operator acts as
|
|
the identity on entries in ess_tdof_list, corresponding to (assembled)
|
|
DIAG_ONE policy or ConstrainedOperator in the matrix-free setting.
|
|
|
|
@note For objects created with this constructor, calling SetOperator()
|
|
will only set the internal Operator pointer to the given new Operator
|
|
without any other changes to the object. This is done to preserve the
|
|
original behavior of this class. */
|
|
OperatorJacobiSmoother(const BilinearForm &a,
|
|
const Array<int> &ess_tdof_list,
|
|
const real_t damping=1.0);
|
|
|
|
/** Application is by the *inverse* of the given vector. It is assumed that
|
|
the underlying operator acts as the identity on entries in ess_tdof_list,
|
|
corresponding to (assembled) DIAG_ONE policy or ConstrainedOperator in
|
|
the matrix-free setting.
|
|
|
|
@note For objects created with this constructor, calling SetOperator()
|
|
will only set the internal Operator pointer to the given new Operator
|
|
without any other changes to the object. This is done to preserve the
|
|
original behavior of this class. */
|
|
OperatorJacobiSmoother(const Vector &d,
|
|
const Array<int> &ess_tdof_list,
|
|
const real_t damping=1.0);
|
|
|
|
~OperatorJacobiSmoother() {}
|
|
|
|
/// Replace diagonal entries with their absolute values.
|
|
void SetPositiveDiagonal(bool pos_diag = true) { use_abs_diag = pos_diag; }
|
|
|
|
/// Approach the solution of the linear system by applying Jacobi smoothing.
|
|
void Mult(const Vector &x, Vector &y) const;
|
|
|
|
/** @brief Approach the solution of the transposed linear system by applying
|
|
Jacobi smoothing. */
|
|
void MultTranspose(const Vector &x, Vector &y) const { Mult(x, y); }
|
|
|
|
/** @brief Recompute the diagonal using the method AssembleDiagonal of the
|
|
given new Operator, @a op. */
|
|
/** Note that (Par)BilinearForm operators are treated similar to the way they
|
|
are treated in the constructor that takes a BilinearForm parameter.
|
|
Specifically, this means that the OperatorJacobiSmoother will work with
|
|
true-dof vectors even though the size of the BilinearForm may be
|
|
different.
|
|
|
|
When the new Operator, @a op, is not a (Par)BilinearForm, any previously
|
|
set array of essential true-dofs will be thrown away because in this case
|
|
any essential b.c. will be handled by the AssembleDiagonal method. */
|
|
void SetOperator(const Operator &op);
|
|
|
|
private:
|
|
Vector dinv;
|
|
const real_t damping;
|
|
const Array<int> *ess_tdof_list; // not owned; may be NULL
|
|
mutable Vector residual;
|
|
/// Uses absolute values of the diagonal entries.
|
|
bool use_abs_diag = false;
|
|
|
|
const Operator *oper; // not owned
|
|
|
|
// To preserve the original behavior, some constructors set this flag to
|
|
// false to disallow updating the OperatorJacobiSmoother with SetOperator.
|
|
const bool allow_updates;
|
|
|
|
public:
|
|
void Setup(const Vector &diag);
|
|
};
|
|
|
|
/// Chebyshev accelerated smoothing with given vector, no matrix necessary
|
|
/** Potentially useful with tensorized operators, for example. This is just a
|
|
very basic Chebyshev iteration, if you want tolerances, iteration control,
|
|
etc. wrap this with SLISolver. */
|
|
class OperatorChebyshevSmoother : public Solver
|
|
{
|
|
public:
|
|
/** Application is by *inverse* of the given vector. It is assumed the
|
|
underlying operator acts as the identity on entries in ess_tdof_list,
|
|
corresponding to (assembled) DIAG_ONE policy or ConstrainedOperator in
|
|
the matrix-free setting. The estimated largest eigenvalue of the
|
|
diagonally preconditoned operator must be provided via
|
|
max_eig_estimate. */
|
|
OperatorChebyshevSmoother(const Operator &oper_, const Vector &d,
|
|
const Array<int>& ess_tdof_list,
|
|
int order, real_t max_eig_estimate);
|
|
|
|
/// Deprecated: see pass-by-reference version above
|
|
MFEM_DEPRECATED
|
|
OperatorChebyshevSmoother(const Operator* oper_, const Vector &d,
|
|
const Array<int>& ess_tdof_list,
|
|
int order, real_t max_eig_estimate);
|
|
|
|
/** Application is by *inverse* of the given vector. It is assumed the
|
|
underlying operator acts as the identity on entries in ess_tdof_list,
|
|
corresponding to (assembled) DIAG_ONE policy or ConstrainedOperator in
|
|
the matrix-free setting. The largest eigenvalue of the diagonally
|
|
preconditoned operator is estimated internally via a power method. The
|
|
accuracy of the estimated eigenvalue may be controlled via
|
|
power_iterations and power_tolerance. */
|
|
#ifdef MFEM_USE_MPI
|
|
OperatorChebyshevSmoother(const Operator &oper_, const Vector &d,
|
|
const Array<int>& ess_tdof_list,
|
|
int order, MPI_Comm comm = MPI_COMM_NULL,
|
|
int power_iterations = 10,
|
|
real_t power_tolerance = 1e-8,
|
|
int power_seed = 12345);
|
|
|
|
/// Deprecated: see pass-by-reference version above
|
|
MFEM_DEPRECATED
|
|
OperatorChebyshevSmoother(const Operator* oper_, const Vector &d,
|
|
const Array<int>& ess_tdof_list,
|
|
int order, MPI_Comm comm = MPI_COMM_NULL,
|
|
int power_iterations = 10,
|
|
real_t power_tolerance = 1e-8);
|
|
#else
|
|
OperatorChebyshevSmoother(const Operator &oper_, const Vector &d,
|
|
const Array<int>& ess_tdof_list,
|
|
int order, int power_iterations = 10,
|
|
real_t power_tolerance = 1e-8,
|
|
int power_seed = 12345);
|
|
|
|
/// Deprecated: see pass-by-reference version above
|
|
MFEM_DEPRECATED
|
|
OperatorChebyshevSmoother(const Operator* oper_, const Vector &d,
|
|
const Array<int>& ess_tdof_list,
|
|
int order, int power_iterations = 10,
|
|
real_t power_tolerance = 1e-8);
|
|
#endif
|
|
|
|
~OperatorChebyshevSmoother() {}
|
|
|
|
/** @brief Approach the solution of the linear system by applying Chebyshev
|
|
smoothing. */
|
|
void Mult(const Vector &x, Vector &y) const;
|
|
|
|
/** @brief Approach the solution of the transposed linear system by applying
|
|
Chebyshev smoothing. */
|
|
void MultTranspose(const Vector &x, Vector &y) const { Mult(x, y); }
|
|
|
|
void SetOperator(const Operator &op_)
|
|
{
|
|
oper = &op_;
|
|
}
|
|
|
|
void Setup();
|
|
|
|
private:
|
|
const int order;
|
|
real_t max_eig_estimate;
|
|
const int N;
|
|
Vector dinv;
|
|
const Vector &diag;
|
|
Array<real_t> coeffs;
|
|
const Array<int>& ess_tdof_list;
|
|
mutable Vector residual;
|
|
mutable Vector helperVector;
|
|
const Operator* oper;
|
|
};
|
|
|
|
|
|
/// Stationary linear iteration: x <- x + B (b - A x)
|
|
class SLISolver : public IterativeSolver
|
|
{
|
|
protected:
|
|
mutable Vector r, z;
|
|
|
|
void UpdateVectors();
|
|
|
|
public:
|
|
SLISolver() { }
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
SLISolver(MPI_Comm comm_) : IterativeSolver(comm_) { }
|
|
#endif
|
|
|
|
void SetOperator(const Operator &op) override
|
|
{ IterativeSolver::SetOperator(op); UpdateVectors(); }
|
|
|
|
/// Iterative solution of the linear system using Stationary Linear Iteration
|
|
/** When using iterative mode (see Solver::iterative_mode), the case of zero
|
|
r.h.s., @a b = 0, can be optimized by calling this method with empty
|
|
@a b, i.e. b.Size() == 0. */
|
|
void Mult(const Vector &b, Vector &x) const override;
|
|
};
|
|
|
|
/// Stationary linear iteration. (tolerances are squared)
|
|
void SLI(const Operator &A, const Vector &b, Vector &x,
|
|
int print_iter = 0, int max_num_iter = 1000,
|
|
real_t RTOLERANCE = 1e-12, real_t ATOLERANCE = 1e-24);
|
|
|
|
/// Preconditioned stationary linear iteration. (tolerances are squared)
|
|
void SLI(const Operator &A, Solver &B, const Vector &b, Vector &x,
|
|
int print_iter = 0, int max_num_iter = 1000,
|
|
real_t RTOLERANCE = 1e-12, real_t ATOLERANCE = 1e-24);
|
|
|
|
|
|
/// Conjugate gradient method
|
|
class CGSolver : public IterativeSolver
|
|
{
|
|
protected:
|
|
mutable Vector r, d, z;
|
|
|
|
void UpdateVectors();
|
|
|
|
public:
|
|
CGSolver() { }
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
CGSolver(MPI_Comm comm_) : IterativeSolver(comm_) { }
|
|
#endif
|
|
|
|
void SetOperator(const Operator &op) override
|
|
{ IterativeSolver::SetOperator(op); UpdateVectors(); }
|
|
|
|
/** @brief Iterative solution of the linear system using the Conjugate
|
|
Gradient method. */
|
|
void Mult(const Vector &b, Vector &x) const override;
|
|
};
|
|
|
|
/// Conjugate gradient method. (tolerances are squared)
|
|
void CG(const Operator &A, const Vector &b, Vector &x,
|
|
int print_iter = 0, int max_num_iter = 1000,
|
|
real_t RTOLERANCE = 1e-12, real_t ATOLERANCE = 1e-24);
|
|
|
|
/// Preconditioned conjugate gradient method. (tolerances are squared)
|
|
void PCG(const Operator &A, Solver &B, const Vector &b, Vector &x,
|
|
int print_iter = 0, int max_num_iter = 1000,
|
|
real_t RTOLERANCE = 1e-12, real_t ATOLERANCE = 1e-24);
|
|
|
|
|
|
/// GMRES method
|
|
class GMRESSolver : public IterativeSolver
|
|
{
|
|
protected:
|
|
int m; // see SetKDim()
|
|
|
|
public:
|
|
GMRESSolver() { m = 50; }
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
GMRESSolver(MPI_Comm comm_) : IterativeSolver(comm_) { m = 50; }
|
|
#endif
|
|
|
|
/// Set the number of iteration to perform between restarts, default is 50.
|
|
void SetKDim(int dim) { m = dim; }
|
|
|
|
/// Iterative solution of the linear system using the GMRES method
|
|
void Mult(const Vector &b, Vector &x) const override;
|
|
};
|
|
|
|
/// FGMRES method
|
|
class FGMRESSolver : public IterativeSolver
|
|
{
|
|
protected:
|
|
int m;
|
|
|
|
public:
|
|
FGMRESSolver() { m = 50; }
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
FGMRESSolver(MPI_Comm comm_) : IterativeSolver(comm_) { m = 50; }
|
|
#endif
|
|
|
|
void SetKDim(int dim) { m = dim; }
|
|
|
|
/// Iterative solution of the linear system using the FGMRES method.
|
|
void Mult(const Vector &b, Vector &x) const override;
|
|
};
|
|
|
|
/// GMRES method. (tolerances are squared)
|
|
int GMRES(const Operator &A, Vector &x, const Vector &b, Solver &M,
|
|
int &max_iter, int m, real_t &tol, real_t atol, int printit);
|
|
|
|
/// GMRES method. (tolerances are squared)
|
|
void GMRES(const Operator &A, Solver &B, const Vector &b, Vector &x,
|
|
int print_iter = 0, int max_num_iter = 1000, int m = 50,
|
|
real_t rtol = 1e-12, real_t atol = 1e-24);
|
|
|
|
|
|
/// BiCGSTAB method
|
|
class BiCGSTABSolver : public IterativeSolver
|
|
{
|
|
protected:
|
|
mutable Vector p, phat, s, shat, t, v, r, rtilde;
|
|
|
|
void UpdateVectors();
|
|
|
|
public:
|
|
BiCGSTABSolver() { }
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
BiCGSTABSolver(MPI_Comm comm_) : IterativeSolver(comm_) { }
|
|
#endif
|
|
|
|
void SetOperator(const Operator &op) override
|
|
{ IterativeSolver::SetOperator(op); UpdateVectors(); }
|
|
|
|
/// Iterative solution of the linear system using the BiCGSTAB method
|
|
void Mult(const Vector &b, Vector &x) const override;
|
|
};
|
|
|
|
/// BiCGSTAB method. (tolerances are squared)
|
|
int BiCGSTAB(const Operator &A, Vector &x, const Vector &b, Solver &M,
|
|
int &max_iter, real_t &tol, real_t atol, int printit);
|
|
|
|
/// BiCGSTAB method. (tolerances are squared)
|
|
void BiCGSTAB(const Operator &A, Solver &B, const Vector &b, Vector &x,
|
|
int print_iter = 0, int max_num_iter = 1000,
|
|
real_t rtol = 1e-12, real_t atol = 1e-24);
|
|
|
|
|
|
/// MINRES method
|
|
class MINRESSolver : public IterativeSolver
|
|
{
|
|
protected:
|
|
mutable Vector v0, v1, w0, w1, q;
|
|
mutable Vector u1; // used in the preconditioned version
|
|
|
|
public:
|
|
MINRESSolver() { }
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
MINRESSolver(MPI_Comm comm_) : IterativeSolver(comm_) { }
|
|
#endif
|
|
|
|
void SetPreconditioner(Solver &pr) override
|
|
{
|
|
IterativeSolver::SetPreconditioner(pr);
|
|
if (oper) { u1.SetSize(width); }
|
|
}
|
|
|
|
void SetOperator(const Operator &op) override;
|
|
|
|
/// Iterative solution of the linear system using the MINRES method
|
|
void Mult(const Vector &b, Vector &x) const override;
|
|
};
|
|
|
|
/// MINRES method without preconditioner. (tolerances are squared)
|
|
void MINRES(const Operator &A, const Vector &b, Vector &x, int print_it = 0,
|
|
int max_it = 1000, real_t rtol = 1e-12, real_t atol = 1e-24);
|
|
|
|
/// MINRES method with preconditioner. (tolerances are squared)
|
|
void MINRES(const Operator &A, Solver &B, const Vector &b, Vector &x,
|
|
int print_it = 0, int max_it = 1000,
|
|
real_t rtol = 1e-12, real_t atol = 1e-24);
|
|
|
|
|
|
/// Newton's method for solving F(x)=b for a given operator F.
|
|
/** The method GetGradient() must be implemented for the operator F.
|
|
The preconditioner is used (in non-iterative mode) to evaluate
|
|
the action of the inverse gradient of the operator. */
|
|
class NewtonSolver : public IterativeSolver
|
|
{
|
|
protected:
|
|
mutable Vector r, c;
|
|
mutable Operator *grad;
|
|
|
|
// Adaptive linear solver rtol variables
|
|
|
|
// Method to determine rtol, 0 means the adaptive algorithm is deactivated.
|
|
int lin_rtol_type = 0;
|
|
// rtol to use in first iteration
|
|
real_t lin_rtol0;
|
|
// Maximum rtol
|
|
real_t lin_rtol_max;
|
|
// Function norm ||F(x)|| of the previous iterate
|
|
mutable real_t fnorm_last = 0.0;
|
|
// Linear residual norm of the previous iterate
|
|
mutable real_t lnorm_last = 0.0;
|
|
// Forcing term (linear residual rtol) from the previous iterate
|
|
mutable real_t eta_last = 0.0;
|
|
// Eisenstat-Walker factor gamma
|
|
real_t gamma;
|
|
// Eisenstat-Walker factor alpha
|
|
real_t alpha;
|
|
|
|
/** @brief Method for the adaptive linear solver rtol invoked before the
|
|
linear solve. */
|
|
void AdaptiveLinRtolPreSolve(const Vector &x,
|
|
const int it,
|
|
const real_t fnorm) const;
|
|
|
|
/** @brief Method for the adaptive linear solver rtol invoked after the
|
|
linear solve. */
|
|
void AdaptiveLinRtolPostSolve(const Vector &x,
|
|
const Vector &b,
|
|
const int it,
|
|
const real_t fnorm) const;
|
|
|
|
public:
|
|
NewtonSolver() { }
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
NewtonSolver(MPI_Comm comm_) : IterativeSolver(comm_) { }
|
|
#endif
|
|
void SetOperator(const Operator &op) override;
|
|
|
|
/// Set the linear solver for inverting the Jacobian.
|
|
/** This method is equivalent to calling SetPreconditioner(). */
|
|
virtual void SetSolver(Solver &solver) { prec = &solver; }
|
|
|
|
/// Solve the nonlinear system with right-hand side @a b.
|
|
/** If `b.Size() != Height()`, then @a b is assumed to be zero. */
|
|
void Mult(const Vector &b, Vector &x) const override;
|
|
|
|
/** @brief This method can be overloaded in derived classes to implement line
|
|
search algorithms. */
|
|
/** The base class implementation (NewtonSolver) simply returns 1. A return
|
|
value of 0 indicates a failure, interrupting the Newton iteration. */
|
|
virtual real_t ComputeScalingFactor(const Vector &x, const Vector &b) const
|
|
{ return 1.0; }
|
|
|
|
/** @brief This method can be overloaded in derived classes to perform
|
|
computations that need knowledge of the newest Newton state. */
|
|
virtual void ProcessNewState(const Vector &x) const { }
|
|
|
|
/// Enable adaptive linear solver relative tolerance algorithm.
|
|
/** Compute a relative tolerance for the Krylov method after each nonlinear
|
|
iteration, based on the algorithm presented in [1].
|
|
|
|
The maximum linear solver relative tolerance @a rtol_max should be < 1. For
|
|
@a type 1 the parameters @a alpha and @a gamma are ignored. For @a type 2
|
|
@a alpha has to be between 0 and 1 and @a gamma between 1 and 2.
|
|
|
|
[1] Eisenstat, Stanley C., and Homer F. Walker. "Choosing the forcing terms
|
|
in an inexact Newton method."
|
|
*/
|
|
void SetAdaptiveLinRtol(const int type = 2,
|
|
const real_t rtol0 = 0.5,
|
|
const real_t rtol_max = 0.9,
|
|
const real_t alpha = 0.5 * (1.0 + sqrt(5.0)),
|
|
const real_t gamma = 1.0);
|
|
};
|
|
|
|
/** L-BFGS method for solving F(x)=b for a given operator F, by minimizing
|
|
the norm of F(x) - b. Requires only the action of the operator F. */
|
|
class LBFGSSolver : public NewtonSolver
|
|
{
|
|
protected:
|
|
int m = 10;
|
|
mutable Array<Vector *> skArray, ykArray;
|
|
|
|
void DeleteStorageVectors()
|
|
{
|
|
for (int i = 0; i < skArray.Size(); i++)
|
|
{
|
|
delete skArray[i];
|
|
delete ykArray[i];
|
|
}
|
|
}
|
|
|
|
void InitializeStorageVectors()
|
|
{
|
|
DeleteStorageVectors();
|
|
skArray.SetSize(m);
|
|
ykArray.SetSize(m);
|
|
for (int i = 0; i < m; i++)
|
|
{
|
|
skArray[i] = new Vector(width);
|
|
ykArray[i] = new Vector(width);
|
|
skArray[i]->UseDevice(true);
|
|
ykArray[i]->UseDevice(true);
|
|
}
|
|
}
|
|
|
|
public:
|
|
LBFGSSolver() : NewtonSolver() { }
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
LBFGSSolver(MPI_Comm comm_) : NewtonSolver(comm_) { }
|
|
#endif
|
|
|
|
void SetOperator(const Operator &op) override
|
|
{
|
|
NewtonSolver::SetOperator(op);
|
|
InitializeStorageVectors();
|
|
}
|
|
|
|
void SetHistorySize(int dim)
|
|
{
|
|
m = dim;
|
|
InitializeStorageVectors();
|
|
}
|
|
|
|
/// Solve the nonlinear system with right-hand side @a b.
|
|
/** If `b.Size() != Height()`, then @a b is assumed to be zero. */
|
|
void Mult(const Vector &b, Vector &x) const override;
|
|
|
|
void SetPreconditioner(Solver &pr) override
|
|
{ MFEM_WARNING("L-BFGS won't use the given preconditioner."); }
|
|
void SetSolver(Solver &solver) override
|
|
{ MFEM_WARNING("L-BFGS won't use the given solver."); }
|
|
|
|
virtual ~LBFGSSolver() { DeleteStorageVectors(); }
|
|
};
|
|
|
|
|
|
/** Adaptive restarted GMRES.
|
|
m_max and m_min(=1) are the maximal and minimal restart parameters.
|
|
m_step(=1) is the step to use for going from m_max and m_min.
|
|
cf(=0.4) is a desired convergence factor. */
|
|
int aGMRES(const Operator &A, Vector &x, const Vector &b,
|
|
const Operator &M, int &max_iter,
|
|
int m_max, int m_min, int m_step, real_t cf,
|
|
real_t &tol, real_t &atol, int printit);
|
|
|
|
#ifdef MFEM_USE_HIOP
|
|
class HiopOptimizationProblem;
|
|
#endif
|
|
|
|
/** Defines operators and constraints for the following optimization problem:
|
|
*
|
|
* Find x that minimizes the objective function F(x), subject to
|
|
* C(x) = c_e,
|
|
* d_lo <= D(x) <= d_hi,
|
|
* x_lo <= x <= x_hi.
|
|
*
|
|
* The operators F, C, D must take input of the same size (same width).
|
|
* Gradients of F, C, D might be needed, depending on the OptimizationSolver.
|
|
* When used with Hiop, gradients of C and D must be DenseMatrices.
|
|
* F always returns a scalar value, see CalcObjective(), CalcObjectiveGrad().
|
|
* C and D can have arbitrary heights.
|
|
* C and D can be NULL, meaning that their constraints are not used.
|
|
*
|
|
* When used in parallel, all Vectors are assumed to be true dof vectors, and
|
|
* the operators are expected to be defined for tdof vectors. */
|
|
class OptimizationProblem
|
|
{
|
|
#ifdef MFEM_USE_HIOP
|
|
friend class HiopOptimizationProblem;
|
|
#endif
|
|
|
|
private:
|
|
/// See NewX().
|
|
mutable bool new_x = true;
|
|
|
|
protected:
|
|
/// Not owned, some can remain unused (NULL).
|
|
const Operator *C, *D;
|
|
const Vector *c_e, *d_lo, *d_hi, *x_lo, *x_hi;
|
|
|
|
/// Implementations of CalcObjective() and CalcObjectiveGrad() can use this
|
|
/// method to check if the argument Vector x has been changed after the last
|
|
/// call to CalcObjective() or CalcObjectiveGrad().
|
|
/// The result is on by default, and gets set by the OptimizationSolver.
|
|
bool NewX() const { return new_x; }
|
|
|
|
public:
|
|
const int input_size;
|
|
|
|
/// In parallel, insize is the number of the local true dofs.
|
|
OptimizationProblem(int insize, const Operator *C_, const Operator *D_);
|
|
|
|
/// Objective F(x). In parallel, the result should be reduced over tasks.
|
|
virtual real_t CalcObjective(const Vector &x) const = 0;
|
|
/// The result grad is expected to enter with the correct size.
|
|
virtual void CalcObjectiveGrad(const Vector &x, Vector &grad) const
|
|
{ MFEM_ABORT("The objective gradient is not implemented."); }
|
|
|
|
void SetEqualityConstraint(const Vector &c);
|
|
void SetInequalityConstraint(const Vector &dl, const Vector &dh);
|
|
void SetSolutionBounds(const Vector &xl, const Vector &xh);
|
|
|
|
const Operator *GetC() const { return C; }
|
|
const Operator *GetD() const { return D; }
|
|
const Vector *GetEqualityVec() const { return c_e; }
|
|
const Vector *GetInequalityVec_Lo() const { return d_lo; }
|
|
const Vector *GetInequalityVec_Hi() const { return d_hi; }
|
|
const Vector *GetBoundsVec_Lo() const { return x_lo; }
|
|
const Vector *GetBoundsVec_Hi() const { return x_hi; }
|
|
|
|
int GetNumConstraints() const;
|
|
};
|
|
|
|
/// Abstract solver for OptimizationProblems.
|
|
class OptimizationSolver : public IterativeSolver
|
|
{
|
|
protected:
|
|
const OptimizationProblem *problem;
|
|
|
|
public:
|
|
OptimizationSolver(): IterativeSolver(), problem(NULL) { }
|
|
#ifdef MFEM_USE_MPI
|
|
OptimizationSolver(MPI_Comm comm_): IterativeSolver(comm_), problem(NULL) { }
|
|
#endif
|
|
~OptimizationSolver() override { }
|
|
|
|
/** This function is virtual as solvers might need to perform some initial
|
|
* actions (e.g. validation) with the OptimizationProblem. */
|
|
virtual void SetOptimizationProblem(const OptimizationProblem &prob)
|
|
{ problem = &prob; }
|
|
|
|
void Mult(const Vector &xt, Vector &x) const override = 0;
|
|
|
|
void SetPreconditioner(Solver &pr) override
|
|
{ MFEM_ABORT("Not meaningful for this solver."); }
|
|
void SetOperator(const Operator &op) override
|
|
{ MFEM_ABORT("Not meaningful for this solver."); }
|
|
};
|
|
|
|
/** SLBQP optimizer:
|
|
* (S)ingle (L)inearly Constrained with (B)ounds (Q)uadratic (P)rogram
|
|
*
|
|
* Minimize || x-x_t ||, subject to
|
|
* sum w_i x_i = a,
|
|
* x_lo <= x <= x_hi.
|
|
*/
|
|
class SLBQPOptimizer : public OptimizationSolver
|
|
{
|
|
protected:
|
|
Vector lo, hi, w;
|
|
real_t a;
|
|
|
|
/// Solve QP at fixed lambda
|
|
inline real_t solve(real_t l, const Vector &xt, Vector &x, int &nclip) const
|
|
{
|
|
add(xt, l, w, x);
|
|
if (problem == NULL) { x.median(lo,hi); }
|
|
else
|
|
{
|
|
x.median(*problem->GetBoundsVec_Lo(),
|
|
*problem->GetBoundsVec_Hi());
|
|
}
|
|
nclip++;
|
|
if (problem == NULL) { return Dot(w, x) - a; }
|
|
else
|
|
{
|
|
Vector c(1);
|
|
// Includes parallel communication.
|
|
problem->GetC()->Mult(x, c);
|
|
|
|
return c(0) - (*problem->GetEqualityVec())(0);
|
|
}
|
|
}
|
|
|
|
inline void print_iteration(int it, real_t r, real_t l) const;
|
|
|
|
public:
|
|
SLBQPOptimizer() { }
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
SLBQPOptimizer(MPI_Comm comm_) : OptimizationSolver(comm_) { }
|
|
#endif
|
|
|
|
/** Setting an OptimizationProblem will overwrite the Vectors given by
|
|
* SetBounds and SetLinearConstraint. The objective function remains
|
|
* unchanged. */
|
|
void SetOptimizationProblem(const OptimizationProblem &prob) override;
|
|
|
|
void SetBounds(const Vector &lo_, const Vector &hi_);
|
|
void SetLinearConstraint(const Vector &w_, real_t a_);
|
|
|
|
/** We let the target values play the role of the initial vector xt, from
|
|
* which the operator generates the optimal vector x. */
|
|
void Mult(const Vector &xt, Vector &x) const override;
|
|
};
|
|
|
|
/** Block ILU solver:
|
|
* Performs a block ILU(k) approximate factorization with specified block
|
|
* size. Currently only k=0 is supported. This is useful as a preconditioner
|
|
* for DG-type discretizations, where the system matrix has a natural
|
|
* (elemental) block structure.
|
|
*
|
|
* In the case of DG discretizations, the block size should usually be set to
|
|
* either ndofs_per_element or vdim*ndofs_per_element (if the finite element
|
|
* space has Ordering::byVDIM). The block size must evenly divide the size of
|
|
* the matrix.
|
|
*
|
|
* Renumbering the blocks is also supported by specifying a reordering method.
|
|
* Currently greedy minimum discarded fill ordering and no reordering are
|
|
* supported. Renumbering the blocks can lead to a much better approximate
|
|
* factorization.
|
|
*/
|
|
class BlockILU : public Solver
|
|
{
|
|
public:
|
|
|
|
/// The reordering method used by the BlockILU factorization.
|
|
enum class Reordering
|
|
{
|
|
MINIMUM_DISCARDED_FILL,
|
|
NONE
|
|
};
|
|
|
|
/** Create an "empty" BlockILU solver. SetOperator must be called later to
|
|
* actually form the factorization
|
|
*/
|
|
BlockILU(int block_size_,
|
|
Reordering reordering_ = Reordering::MINIMUM_DISCARDED_FILL,
|
|
int k_fill_ = 0);
|
|
|
|
/** Create a block ILU approximate factorization for the matrix @a op.
|
|
* @a op should be of type either SparseMatrix or HypreParMatrix. In the
|
|
* case that @a op is a HypreParMatrix, the ILU factorization is performed
|
|
* on the diagonal blocks of the parallel decomposition.
|
|
*/
|
|
BlockILU(const Operator &op, int block_size_ = 1,
|
|
Reordering reordering_ = Reordering::MINIMUM_DISCARDED_FILL,
|
|
int k_fill_ = 0);
|
|
|
|
/** Perform the block ILU factorization for the matrix @a op.
|
|
* As in the constructor, @a op must either be a SparseMatrix or
|
|
* HypreParMatrix
|
|
*/
|
|
void SetOperator(const Operator &op);
|
|
|
|
/// Solve the system `LUx = b`, where `L` and `U` are the block ILU factors.
|
|
void Mult(const Vector &b, Vector &x) const;
|
|
|
|
/** Get the I array for the block CSR representation of the factorization.
|
|
* Similar to SparseMatrix::GetI(). Mostly used for testing.
|
|
*/
|
|
int *GetBlockI() { return IB.GetData(); }
|
|
|
|
/** Get the J array for the block CSR representation of the factorization.
|
|
* Similar to SparseMatrix::GetJ(). Mostly used for testing.
|
|
*/
|
|
int *GetBlockJ() { return JB.GetData(); }
|
|
|
|
/** Get the data array for the block CSR representation of the factorization.
|
|
* Similar to SparseMatrix::GetData(). Mostly used for testing.
|
|
*/
|
|
real_t *GetBlockData() { return AB.Data(); }
|
|
|
|
private:
|
|
/// Set up the block CSR structure corresponding to a sparse matrix @a A
|
|
void CreateBlockPattern(const class SparseMatrix &A);
|
|
|
|
/// Perform the block ILU factorization
|
|
void Factorize();
|
|
|
|
int block_size;
|
|
|
|
/// Fill level for block ILU(k) factorizations. Only k=0 is supported.
|
|
int k_fill;
|
|
|
|
Reordering reordering;
|
|
|
|
/// Temporary vector used in the Mult() function.
|
|
mutable Vector y;
|
|
|
|
/// Permutation and inverse permutation vectors for the block reordering.
|
|
Array<int> P, Pinv;
|
|
|
|
/** Block CSR storage of the factorization. The block upper triangular part
|
|
* stores the U factor. The L factor implicitly has identity on the diagonal
|
|
* blocks, and the rest of L is given by the strictly block lower triangular
|
|
* part.
|
|
*/
|
|
Array<int> IB, ID, JB;
|
|
DenseTensor AB;
|
|
|
|
/// DB(i) stores the LU factorization of the i'th diagonal block
|
|
mutable DenseTensor DB;
|
|
/// Pivot arrays for the LU factorizations given by #DB
|
|
mutable Array<int> ipiv;
|
|
};
|
|
|
|
|
|
/// Monitor that checks whether the residual is zero at a given set of dofs.
|
|
/** This monitor is useful for checking if the initial guess, rhs, operator, and
|
|
preconditioner are properly setup for solving in the subspace with imposed
|
|
essential boundary conditions. */
|
|
class ResidualBCMonitor : public IterativeSolverMonitor
|
|
{
|
|
protected:
|
|
const Array<int> *ess_dofs_list; ///< Not owned
|
|
|
|
public:
|
|
ResidualBCMonitor(const Array<int> &ess_dofs_list_)
|
|
: ess_dofs_list(&ess_dofs_list_) { }
|
|
|
|
void MonitorResidual(int it, real_t norm, const Vector &r,
|
|
bool final) override;
|
|
};
|
|
|
|
|
|
#ifdef MFEM_USE_SUITESPARSE
|
|
|
|
/// Direct sparse solver using UMFPACK
|
|
class UMFPackSolver : public Solver
|
|
{
|
|
protected:
|
|
bool use_long_ints;
|
|
SparseMatrix *mat;
|
|
void *Numeric;
|
|
SuiteSparse_long *AI, *AJ;
|
|
|
|
void Init();
|
|
|
|
public:
|
|
real_t Control[UMFPACK_CONTROL];
|
|
mutable real_t Info[UMFPACK_INFO];
|
|
|
|
/** @brief For larger matrices, if the solver fails, set the parameter @a
|
|
use_long_ints_ = true. */
|
|
UMFPackSolver(bool use_long_ints_ = false)
|
|
: use_long_ints(use_long_ints_) { Init(); }
|
|
/** @brief Factorize the given SparseMatrix using the defaults. For larger
|
|
matrices, if the solver fails, set the parameter @a use_long_ints_ =
|
|
true. */
|
|
UMFPackSolver(SparseMatrix &A, bool use_long_ints_ = false)
|
|
: use_long_ints(use_long_ints_) { Init(); SetOperator(A); }
|
|
|
|
/** @brief Factorize the given Operator @a op which must be a SparseMatrix.
|
|
|
|
The factorization uses the parameters set in the #Control data member.
|
|
@note This method calls SparseMatrix::SortColumnIndices() with @a op,
|
|
modifying the matrix if the column indices are not already sorted. */
|
|
void SetOperator(const Operator &op) override;
|
|
|
|
/// Set the print level field in the #Control data member.
|
|
void SetPrintLevel(int print_lvl) { Control[UMFPACK_PRL] = print_lvl; }
|
|
|
|
/// Direct solution of the linear system using UMFPACK
|
|
void Mult(const Vector &b, Vector &x) const override;
|
|
|
|
/// Direct solution of the transposed linear system using UMFPACK
|
|
void MultTranspose(const Vector &b, Vector &x) const override;
|
|
|
|
virtual ~UMFPackSolver();
|
|
};
|
|
|
|
/// Direct sparse solver using KLU
|
|
class KLUSolver : public Solver
|
|
{
|
|
protected:
|
|
SparseMatrix *mat;
|
|
klu_symbolic *Symbolic;
|
|
klu_numeric *Numeric;
|
|
|
|
void Init();
|
|
|
|
public:
|
|
KLUSolver()
|
|
: mat(0),Symbolic(0),Numeric(0)
|
|
{ Init(); }
|
|
KLUSolver(SparseMatrix &A)
|
|
: mat(0),Symbolic(0),Numeric(0)
|
|
{ Init(); SetOperator(A); }
|
|
|
|
// Works on sparse matrices only; calls SparseMatrix::SortColumnIndices().
|
|
void SetOperator(const Operator &op) override;
|
|
|
|
/// Direct solution of the linear system using KLU
|
|
void Mult(const Vector &b, Vector &x) const override;
|
|
|
|
/// Direct solution of the transposed linear system using KLU
|
|
void MultTranspose(const Vector &b, Vector &x) const override;
|
|
|
|
virtual ~KLUSolver();
|
|
|
|
mutable klu_common Common;
|
|
};
|
|
|
|
#endif // MFEM_USE_SUITESPARSE
|
|
|
|
/// Block diagonal solver for A, each block is inverted by direct solver
|
|
class DirectSubBlockSolver : public Solver
|
|
{
|
|
SparseMatrix& block_dof;
|
|
mutable Array<int> local_dofs;
|
|
mutable Vector sub_rhs;
|
|
mutable Vector sub_sol;
|
|
std::unique_ptr<DenseMatrixInverse[]> block_solvers;
|
|
public:
|
|
/// block_dof is a boolean matrix, block_dof(i, j) = 1 if j-th dof belongs to
|
|
/// i-th block, block_dof(i, j) = 0 otherwise.
|
|
DirectSubBlockSolver(const SparseMatrix& A, const SparseMatrix& block_dof);
|
|
|
|
/// Direct solution of the block diagonal linear system
|
|
void Mult(const Vector &x, Vector &y) const override;
|
|
void SetOperator(const Operator &op) override { }
|
|
};
|
|
|
|
/// Solver S such that I - A * S = (I - A * S1) * (I - A * S0).
|
|
/// That is, S = S0 + S1 - S1 * A * S0.
|
|
class ProductSolver : public Solver
|
|
{
|
|
OperatorPtr A;
|
|
OperatorPtr S0;
|
|
OperatorPtr S1;
|
|
public:
|
|
ProductSolver(Operator* A_, Solver* S0_, Solver* S1_,
|
|
bool ownA, bool ownS0, bool ownS1)
|
|
: Solver(A_->NumRows()), A(A_, ownA), S0(S0_, ownS0), S1(S1_, ownS1) { }
|
|
|
|
/// Solution of the linear system using a product of subsolvers
|
|
void Mult(const Vector &x, Vector &y) const override;
|
|
|
|
/// Solution of the transposed linear system using a product of subsolvers
|
|
void MultTranspose(const Vector &x, Vector &y) const override;
|
|
void SetOperator(const Operator &op) override { }
|
|
};
|
|
|
|
/// Solver wrapper which orthogonalizes the input and output vector
|
|
/**
|
|
* OrthoSolver wraps an existing Solver and orthogonalizes the input vector
|
|
* before passing it to the Mult() method of the Solver. This is a convenience
|
|
* implementation to handle e.g. a Poisson problem with pure Neumann boundary
|
|
* conditions, where this procedure removes the Nullspace.
|
|
*/
|
|
class OrthoSolver : public Solver
|
|
{
|
|
private:
|
|
#ifdef MFEM_USE_MPI
|
|
MPI_Comm mycomm;
|
|
mutable HYPRE_BigInt global_size;
|
|
const bool parallel;
|
|
#else
|
|
mutable int global_size;
|
|
#endif
|
|
|
|
public:
|
|
OrthoSolver();
|
|
#ifdef MFEM_USE_MPI
|
|
OrthoSolver(MPI_Comm mycomm_);
|
|
#endif
|
|
|
|
/// Set the solver used by the OrthoSolver.
|
|
/** The action of the OrthoSolver is given by P * s * P where P is the
|
|
projection to the subspace of vectors with zero sum. Calling this method
|
|
is required before calling SetOperator() or Mult(). */
|
|
void SetSolver(Solver &s);
|
|
|
|
/// Set the Operator that is the OrthoSolver is to invert (approximately).
|
|
/** The Operator @a op is simply forwarded to the solver object given by
|
|
SetSolver() which needs to be called before this method. Calling this
|
|
method is optional when the solver already has an associated Operator. */
|
|
void SetOperator(const Operator &op) override;
|
|
|
|
/** @brief Perform the action of the OrthoSolver: P * solver * P where P is
|
|
the projection to the subspace of vectors with zero sum. */
|
|
/** @note The projection P can be written as P = I - 1 1^T / (1^T 1) where
|
|
I is the identity matrix and 1 is the column-vector with all components
|
|
equal to 1. */
|
|
void Mult(const Vector &b, Vector &x) const override;
|
|
|
|
private:
|
|
Solver *solver = nullptr;
|
|
|
|
mutable Vector b_ortho;
|
|
|
|
void Orthogonalize(const Vector &v, Vector &v_ortho) const;
|
|
};
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
/** This smoother does relaxations on an auxiliary space (determined by a map
|
|
from the original space to the auxiliary space provided by the user).
|
|
The smoother on the auxiliary space is a HypreSmoother. Its options can be
|
|
modified through GetSmoother.
|
|
For example, the space can be the nullspace of div/curl, in which case the
|
|
smoother can be used to construct a Hiptmair smoother. */
|
|
class AuxSpaceSmoother : public Solver
|
|
{
|
|
OperatorPtr aux_map_;
|
|
OperatorPtr aux_system_;
|
|
OperatorPtr aux_smoother_;
|
|
void Mult(const Vector &x, Vector &y, bool transpose) const;
|
|
public:
|
|
AuxSpaceSmoother(const HypreParMatrix &op, HypreParMatrix *aux_map,
|
|
bool op_is_symmetric = true, bool own_aux_map = false);
|
|
void Mult(const Vector &x, Vector &y) const override { Mult(x, y, false); }
|
|
void MultTranspose(const Vector &x, Vector &y) const override
|
|
{ Mult(x, y, true); }
|
|
void SetOperator(const Operator &op) override { }
|
|
HypreSmoother& GetSmoother() { return *aux_smoother_.As<HypreSmoother>(); }
|
|
using Operator::Mult;
|
|
};
|
|
#endif // MFEM_USE_MPI
|
|
|
|
#ifdef MFEM_USE_LAPACK
|
|
/** Non-negative least squares (NNLS) solver class, for computing a vector
|
|
with non-negative entries approximately satisfying an under-determined
|
|
linear system. */
|
|
class NNLSSolver : public Solver
|
|
{
|
|
public:
|
|
NNLSSolver();
|
|
|
|
~NNLSSolver() { }
|
|
|
|
/// The operator must be a DenseMatrix.
|
|
void SetOperator(const Operator &op) override;
|
|
|
|
/** @brief Compute the non-negative least squares solution to the
|
|
underdetermined system. */
|
|
void Mult(const Vector &w, Vector &sol) const override;
|
|
|
|
/** @brief
|
|
Set verbosity. If set to 0: print nothing; if 1: just print results;
|
|
if 2: print short update on every iteration; if 3: print longer update
|
|
each iteration.
|
|
*/
|
|
void SetVerbosity(int v) { verbosity_ = v; }
|
|
|
|
/// Set the target absolute residual norm tolerance for convergence
|
|
void SetTolerance(real_t tol) { const_tol_ = tol; }
|
|
|
|
/// Set the minimum number of nonzeros required for the solution.
|
|
void SetMinNNZ(int min_nnz) { min_nnz_ = min_nnz; }
|
|
|
|
/** @brief Set the maximum number of nonzeros required for the solution, as
|
|
an early termination condition. */
|
|
void SetMaxNNZ(int max_nnz) { max_nnz_ = max_nnz; }
|
|
|
|
/** @brief Set threshold on relative change in residual over nStallCheck_
|
|
iterations. */
|
|
void SetResidualChangeTolerance(real_t tol)
|
|
{ res_change_termination_tol_ = tol; }
|
|
|
|
/** @brief Set the magnitude of projected residual entries that are
|
|
considered zero. Increasing this value relaxes solution constraints. */
|
|
void SetZeroTolerance(real_t tol) { zero_tol_ = tol; }
|
|
|
|
/// Set RHS vector constant shift, defining rhs_lb and rhs_ub in Solve().
|
|
void SetRHSDelta(real_t d) { rhs_delta_ = d; }
|
|
|
|
/// Set the maximum number of outer iterations in Solve().
|
|
void SetOuterIterations(int n) { n_outer_ = n; }
|
|
|
|
/// Set the maximum number of inner iterations in Solve().
|
|
void SetInnerIterations(int n) { n_inner_ = n; }
|
|
|
|
/// Set the number of iterations to use for stall checking.
|
|
void SetStallCheck(int n) { nStallCheck_ = n; }
|
|
|
|
/// Set a flag to determine whether to call NormalizeConstraints().
|
|
void SetNormalize(bool n) { normalize_ = n; }
|
|
|
|
/** @brief
|
|
* Enumerated types of QRresidual mode. Options are 'off': the residual is
|
|
* calculated normally, 'on': the residual is calculated using the QR
|
|
* method, 'hybrid': the residual is calculated normally until we experience
|
|
* rounding errors, then the QR method is used. The default is 'hybrid',
|
|
* which should see the best performance. Recommend using 'hybrid' or 'off'
|
|
* only, since 'on' is computationally expensive.
|
|
*/
|
|
enum class QRresidualMode {off, on, hybrid};
|
|
|
|
/** @brief
|
|
* Set the residual calculation mode for the NNLS solver. See QRresidualMode
|
|
* enum above for details.
|
|
*/
|
|
void SetQRResidualMode(const QRresidualMode qr_residual_mode);
|
|
|
|
/**
|
|
* @brief Solve the NNLS problem. Specifically, we find a vector @a soln,
|
|
* such that rhs_lb < mat*soln < rhs_ub is satisfied, where mat is the
|
|
* DenseMatrix input to SetOperator().
|
|
*
|
|
* The method by which we find the solution is the active-set method
|
|
* developed by Lawson and Hanson (1974) using lapack. To decrease rounding
|
|
* errors in the case of very tight tolerances, we have the option to compute
|
|
* the residual using the QR factorization of A, by res = b - Q*Q^T*b. This
|
|
* residual calculation results in less rounding error, but is more
|
|
* computationally expensive. To select whether to use the QR residual method
|
|
* or not, see set_qrresidual_mode above.
|
|
*/
|
|
void Solve(const Vector& rhs_lb, const Vector& rhs_ub, Vector& soln) const;
|
|
|
|
/** @brief
|
|
* Normalize the constraints such that the tolerances for each constraint
|
|
* (i.e. (UB - LB)/2) are equal. This seems to help the performance in most
|
|
* cases.
|
|
*/
|
|
void NormalizeConstraints(Vector& rhs_lb, Vector& rhs_ub) const;
|
|
|
|
private:
|
|
const DenseMatrix *mat;
|
|
|
|
real_t const_tol_;
|
|
int min_nnz_; // minimum number of nonzero entries
|
|
mutable int max_nnz_; // maximum number of nonzero entries
|
|
int verbosity_;
|
|
|
|
/**
|
|
* @brief Threshold on relative change in residual over nStallCheck_
|
|
* iterations, for stall sensing.
|
|
*/
|
|
real_t res_change_termination_tol_;
|
|
|
|
real_t zero_tol_;
|
|
real_t rhs_delta_;
|
|
int n_outer_;
|
|
int n_inner_;
|
|
int nStallCheck_;
|
|
|
|
bool normalize_;
|
|
|
|
mutable bool NNLS_qrres_on_;
|
|
QRresidualMode qr_residual_mode_;
|
|
|
|
mutable Vector row_scaling_;
|
|
};
|
|
#endif // MFEM_USE_LAPACK
|
|
|
|
}
|
|
|
|
#endif // MFEM_SOLVERS
|