Loading [MathJax]/extensions/tex2jax.js
libMesh
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Pages
eigen_sparse_linear_solver.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2025 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 
18 
19 
20 #include "libmesh/libmesh_common.h"
21 
22 #ifdef LIBMESH_HAVE_EIGEN
23 
24 
25 // Local Includes
26 #include "libmesh/eigen_sparse_linear_solver.h"
27 #include "libmesh/libmesh_logging.h"
28 #include "libmesh/enum_to_string.h"
29 #include "libmesh/solver_configuration.h"
30 #include "libmesh/enum_preconditioner_type.h"
31 #include "libmesh/enum_solver_type.h"
32 
33 // GMRES is an "unsupported" iterative solver in Eigen.
34 #include "libmesh/ignore_warnings.h"
35 #include <unsupported/Eigen/IterativeSolvers>
36 #include "libmesh/restore_warnings.h"
37 
38 namespace libMesh
39 {
40 
41 template <typename T>
44  LinearSolver<T>(comm_in),
45  _comp_info(Eigen::Success)
46 {
47  // The GMRES _solver_type can be used in EigenSparseLinearSolver,
48  // however, the GMRES iterative solver is currently in the Eigen
49  // "unsupported" directory, so we use BICGSTAB as our default.
50  this->_solver_type = BICGSTAB;
51 }
52 
53 
54 
55 template <typename T>
57 {
58  if (this->initialized())
59  {
60  this->_is_initialized = false;
61 
62  this->_solver_type = BICGSTAB;
63  this->_preconditioner_type = ILU_PRECOND;
64  }
65 }
66 
67 
68 
69 template <typename T>
70 void EigenSparseLinearSolver<T>::init (const char * /*name*/)
71 {
72  // Initialize the data structures if not done so already.
73  if (!this->initialized())
74  {
75  this->_is_initialized = true;
76  }
77 }
78 
79 
80 
81 template <typename T>
82 std::pair<unsigned int, Real>
84  NumericVector<T> & solution_in,
85  NumericVector<T> & rhs_in,
86  const std::optional<double> tol,
87  const std::optional<unsigned int> m_its)
88 {
89  LOG_SCOPE("solve()", "EigenSparseLinearSolver");
90  this->init ();
91 
92  // Make sure the data passed in are really Eigen types
93  EigenSparseMatrix<T> & matrix = cast_ref<EigenSparseMatrix<T> &>(matrix_in);
94  EigenSparseVector<T> & solution = cast_ref<EigenSparseVector<T> &>(solution_in);
95  EigenSparseVector<T> & rhs = cast_ref<EigenSparseVector<T> &>(rhs_in);
96 
97  // Close the matrix and vectors in case this wasn't already done.
98  matrix.close();
99  solution.close();
100  rhs.close();
101 
102  std::pair<unsigned int, Real> retval(0,0.);
103 
104  // Eigen doesn't give us a solver base class? We'll just use a
105  // generic lambda, then.
106  auto do_solve = [this, &rhs, &solution, tol, m_its]
107  (auto & e_solver, std::string_view msg) {
108  const int max_its = this->get_int_solver_setting("max_its", m_its);
109  const double abs_tol = this->get_real_solver_setting("abs_tol", tol);
110 
111  e_solver.setMaxIterations(max_its);
112  e_solver.setTolerance(abs_tol);
113  libMesh::out << msg << std::endl;
114 
115  solution._vec = e_solver.solveWithGuess(rhs._vec,solution._vec);
116 
117  libMesh::out << "#iterations: " << e_solver.iterations() << " / " << max_its << std::endl;
118  libMesh::out << "estimated error: " << e_solver.error() << " / " << abs_tol << std::endl;
119  _comp_info = e_solver.info();
120  return std::make_pair(e_solver.iterations(), e_solver.error());
121  };
122 
123  using Eigen::DiagonalPreconditioner;
124  using Eigen::IdentityPreconditioner;
125  using Eigen::IncompleteCholesky;
126  using Eigen::IncompleteLUT;
127 
128  // Solve the linear system
129  switch (this->_solver_type)
130  {
131  // Conjugate-Gradient
132  case CG:
133  {
134  const int UPLO = Eigen::Lower|Eigen::Upper;
135 
136  switch (this->_preconditioner_type)
137  {
138  case IDENTITY_PRECOND:
139  {
140  Eigen::ConjugateGradient<EigenSM,UPLO,IdentityPreconditioner> solver (matrix._mat);
141  retval = do_solve(solver, "Eigen CG solver without preconditioning");
142  break;
143  }
144  case ILU_PRECOND:
145  {
146  Eigen::ConjugateGradient<EigenSM,UPLO,IncompleteLUT<Number,eigen_idx_type>>
147  solver (matrix._mat);
148  retval = do_solve(solver, "Eigen CG solver with Incomplete Cholesky preconditioning");
149  break;
150  }
151  case ICC_PRECOND:
152  {
153  Eigen::ConjugateGradient<EigenSM,UPLO,
154  IncompleteCholesky<Number,Eigen::Lower,Eigen::AMDOrdering<eigen_idx_type>>>
155  solver (matrix._mat);
156  retval = do_solve(solver, "Eigen CG solver with Incomplete Cholesky preconditioning");
157  break;
158  }
159  default: // For our default let's use their default
160  libmesh_warning("No EigenSparseLinearSolver support for " <<
161  Utility::enum_to_string<PreconditionerType>(this->_preconditioner_type)
162  << " preconditioning.");
163  libmesh_fallthrough();
164  case JACOBI_PRECOND:
165  {
166  Eigen::ConjugateGradient<EigenSM,UPLO,DiagonalPreconditioner<Number>> solver (matrix._mat);
167  retval = do_solve(solver, "Eigen CG solver with Jacobi preconditioning");
168  break;
169  }
170  }
171  break;
172  }
173 
174  // Bi-Conjugate Gradient Stabilized
175  case BICGSTAB:
176  {
177  switch (this->_preconditioner_type)
178  {
179  case IDENTITY_PRECOND:
180  {
181  Eigen::BiCGSTAB<EigenSM, IdentityPreconditioner> solver (matrix._mat);
182  retval = do_solve(solver, "Eigen BiCGStab solver");
183  break;
184  }
185  case ICC_PRECOND:
186  case ILU_PRECOND:
187  {
188  Eigen::BiCGSTAB<EigenSM,IncompleteLUT<Number, eigen_idx_type>>
189  solver (matrix._mat);
190  retval = do_solve(solver, "Eigen BiCGSTAB solver with ILU preconditioning");
191  break;
192  }
193  default: // For our default let's use their default
194  libmesh_warning("No EigenSparseLinearSolver support for " <<
195  Utility::enum_to_string<PreconditionerType>(this->_preconditioner_type)
196  << " preconditioning.");
197  libmesh_fallthrough();
198  case JACOBI_PRECOND:
199  {
200  Eigen::BiCGSTAB<EigenSM,DiagonalPreconditioner<Number>> solver (matrix._mat);
201  retval = do_solve(solver, "Eigen BiCGSTAB solver with Jacobi preconditioning");
202  break;
203  }
204  }
205  break;
206  }
207 
208  // Generalized Minimum Residual
209  case GMRES:
210  {
211  auto set_restart_and_solve = [this, &do_solve]
212  (auto & gm_solver, std::string_view msg)
213  {
214  // If there is an int parameter called "gmres_restart" in the
215  // SolverConfiguration object, pass it to the Eigen GMRES
216  // solver.
217  if (this->_solver_configuration)
218  if (const auto it = this->_solver_configuration->int_valued_data.find("gmres_restart");
219  it != this->_solver_configuration->int_valued_data.end())
220  gm_solver.set_restart(it->second);
221 
222  std::ostringstream full_msg;
223  full_msg << msg << ", restart = " << gm_solver.get_restart();
224  return do_solve(gm_solver, full_msg.str());
225  };
226 
227  switch (this->_preconditioner_type)
228  {
229  case IDENTITY_PRECOND:
230  {
231  Eigen::GMRES<EigenSM,IdentityPreconditioner> solver (matrix._mat);
232  retval = set_restart_and_solve(solver, "Eigen GMRES solver without preconditioning");
233  break;
234  }
235  case ICC_PRECOND:
236  case ILU_PRECOND:
237  {
238  Eigen::GMRES<EigenSM,IncompleteLUT<Number, eigen_idx_type>>
239  solver (matrix._mat);
240  retval = set_restart_and_solve(solver, "Eigen GMRES solver with ILU preconditioning");
241  break;
242  }
243  default: // For our default let's use their default
244  libmesh_warning("No EigenSparseLinearSolver support for " <<
245  Utility::enum_to_string<PreconditionerType>(this->_preconditioner_type)
246  << " preconditioning.");
247  libmesh_fallthrough();
248  case JACOBI_PRECOND:
249  {
250  Eigen::GMRES<EigenSM,DiagonalPreconditioner<Number>> solver (matrix._mat);
251  retval = set_restart_and_solve(solver, "Eigen CG solver with Jacobi preconditioning");
252  break;
253  }
254  }
255  break;
256  }
257 
258  case SPARSELU:
259  {
260  // SparseLU solver code adapted from:
261  // http://eigen.tuxfamily.org/dox-devel/classEigen_1_1SparseLU.html
262  //
263  // From Eigen docs:
264  // The input matrix A should be in a compressed and
265  // column-major form. Otherwise an expensive copy will be
266  // made. You can call the inexpensive makeCompressed() to get
267  // a compressed matrix.
268  //
269  // Note: we don't have a column-major storage format here, so
270  // I think a copy must be made in order to use SparseLU. It
271  // appears that we also have to call makeCompressed(),
272  // otherwise you get a segfault.
273  matrix._mat.makeCompressed();
274 
275  // Build the SparseLU solver object. Note, there is one other
276  // sparse direct solver available in Eigen:
277  //
278  // Eigen::SparseQR<EigenSM, Eigen::AMDOrdering<int>> solver;
279  //
280  // I've tested it, and it works, but it is much slower than
281  // SparseLU. The main benefit of SparseQR is that it can
282  // handle non-square matrices, but we don't allow non-square
283  // sparse matrices to be built in libmesh...
284  Eigen::SparseLU<EigenSM> solver;
285 
286  libMesh::out << "Eigen Sparse LU solver" << std::endl;
287 
288  // Compute the ordering permutation vector from the structural pattern of the matrix.
289  solver.analyzePattern(matrix._mat);
290 
291  // Compute the numerical factorization
292  solver.factorize(matrix._mat);
293 
294  // Use the factors to solve the linear system
295  solution._vec = solver.solve(rhs._vec);
296 
297  // Set up the return value. The SparseLU solver doesn't
298  // support asking for the number of iterations or the final
299  // error, so we'll just report back 1 and 0, respectively.
300  retval = std::make_pair(/*n. iterations=*/1, /*error=*/0);
301 
302  // Store the success/failure reason and break out.
303  _comp_info = solver.info();
304  break;
305  }
306 
307  // Unknown solver, use BICGSTAB
308  default:
309  {
310  libMesh::err << "ERROR: Unsupported Eigen Solver: "
311  << Utility::enum_to_string(this->_solver_type) << std::endl
312  << "Continuing with BICGSTAB" << std::endl;
313 
314  this->_solver_type = BICGSTAB;
315 
316  return this->solve (matrix,
317  solution,
318  rhs,
319  tol,
320  m_its);
321  }
322  }
323 
324  return retval;
325 }
326 
327 
328 
329 template <typename T>
330 std::pair<unsigned int, Real>
332  NumericVector<T> & solution_in,
333  NumericVector<T> & rhs_in,
334  const std::optional<double> tol,
335  const std::optional<unsigned int> m_its)
336 {
337  LOG_SCOPE("adjoint_solve()", "EigenSparseLinearSolver");
338 
339  libmesh_experimental();
340  EigenSparseMatrix<T> mat_trans(this->comm());
341  matrix_in.get_transpose(mat_trans);
342 
343  std::pair<unsigned int, Real> retval = this->solve (mat_trans,
344  solution_in,
345  rhs_in,
346  tol,
347  m_its);
348 
349  return retval;
350 }
351 
352 
353 
354 
355 template <typename T>
356 std::pair<unsigned int, Real>
358  NumericVector<T> & /*solution_in*/,
359  NumericVector<T> & /*rhs_in*/,
360  const std::optional<double> /*tol*/,
361  const std::optional<unsigned int> /*m_its*/)
362 {
363  libmesh_not_implemented();
364  return std::make_pair(0,0.0);
365 }
366 
367 
368 
369 template <typename T>
370 std::pair<unsigned int, Real>
372  const SparseMatrix<T> & /*precond_matrix*/,
373  NumericVector<T> & /*solution_in*/,
374  NumericVector<T> & /*rhs_in*/,
375  const std::optional<double> /*tol*/,
376  const std::optional<unsigned int> /*m_its*/)
377 {
378  libmesh_not_implemented();
379  return std::make_pair(0,0.0);
380 }
381 
382 
383 
384 template <typename T>
386 {
387  libmesh_not_implemented();
388 
389  // switch (this->_preconditioner_type)
390  // {
391  // case IDENTITY_PRECOND:
392  // _precond_type = nullptr; return;
393 
394  // case ILU_PRECOND:
395  // _precond_type = ILUPrecond; return;
396 
397  // case JACOBI_PRECOND:
398  // _precond_type = JacobiPrecond; return;
399 
400  // case SSOR_PRECOND:
401  // _precond_type = SSORPrecond; return;
402 
403 
404  // default:
405  // libMesh::err << "ERROR: Unsupported LASPACK Preconditioner: "
406  // << this->_preconditioner_type << std::endl
407  // << "Continuing with ILU" << std::endl;
408  // this->_preconditioner_type = ILU_PRECOND;
409  // this->set_laspack_preconditioner_type();
410  // }
411 }
412 
413 
414 
415 template <typename T>
417 {
418  // If later versions of Eigen start returning new enumerations,
419  // we'll need to add them to the map...
420  if (auto it = _convergence_reasons.find(_comp_info);
421  it == _convergence_reasons.end())
422  {
423  libmesh_warning("Warning: unknown Eigen::ComputationInfo: " \
424  << _comp_info \
425  << " returning CONVERGED_ITS." \
426  << std::endl);
427  return CONVERGED_ITS;
428  }
429  else
430  return it->second;
431 }
432 
433 
434 
435 //------------------------------------------------------------------
436 // Explicit instantiations
437 template class LIBMESH_EXPORT EigenSparseLinearSolver<Number>;
438 
439 } // namespace libMesh
440 
441 
442 #endif // #ifdef LIBMESH_HAVE_EIGEN
OStreamProxy err
SolverType _solver_type
Enum stating which type of iterative solver to use.
The EigenSparseMatrix class wraps a sparse matrix object from the Eigen library.
virtual void close() override
Calls the SparseMatrix&#39;s internal assembly routines, ensuring that the values are consistent across p...
EigenSparseLinearSolver(const libMesh::Parallel::Communicator &comm_in)
Constructor.
This class provides an interface to Eigen iterative solvers that is compatible with the libMesh Linea...
LinearConvergenceReason
Linear solver convergence flags (taken from the PETSc flags).
Provides a uniform interface to vector storage schemes for different linear algebra libraries...
Definition: vector_fe_ex5.C:44
virtual void init(const char *name=nullptr) override
Initialize data structures if not done so already.
The libMesh namespace provides an interface to certain functionality in the library.
DataType _mat
Actual Eigen::SparseMatrix<> we are wrapping.
Generic sparse matrix.
Definition: vector_fe_ex5.C:46
This base class can be inherited from to provide interfaces to linear solvers from different packages...
bool _is_initialized
Flag that tells if init() has been called.
Definition: libmesh.C:257
void init(triangulateio &t)
Initializes the fields of t to nullptr/0 as necessary.
Eigen::SparseMatrix< Number, Eigen::RowMajor, eigen_idx_type > EigenSM
virtual LinearConvergenceReason get_converged_reason() const override
virtual std::pair< unsigned int, Real > solve(SparseMatrix< T > &matrix, NumericVector< T > &solution, NumericVector< T > &rhs, const std::optional< double > tol=std::nullopt, const std::optional< unsigned int > m_its=std::nullopt) override
Call the Eigen solver.
std::string enum_to_string(const T e)
virtual void clear() override
Release all memory and clear data structures.
void set_eigen_preconditioner_type()
Tells Eigen to use the user-specified preconditioner stored in _preconditioner_type.
OStreamProxy out
virtual void get_transpose(SparseMatrix< T > &dest) const =0
Copies the transpose of the matrix into dest, which may be *this.
virtual std::pair< unsigned int, Real > adjoint_solve(SparseMatrix< T > &matrix, NumericVector< T > &solution, NumericVector< T > &rhs, const std::optional< double > tol=std::nullopt, const std::optional< unsigned int > m_its=std::nullopt) override
Call the Eigen solver to solve A^T x = b.
bool initialized()
Checks that library initialization has been done.
Definition: libmesh.C:276
Generic shell matrix, i.e.
This class provides a nice interface to the Eigen C++-based data structures for serial vectors...