https://mooseframework.inl.gov
NavierStokesProblem.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 #include "NavierStokesProblem.h"
11 #include "NonlinearSystemBase.h"
13 #include "libmesh/petsc_matrix.h"
14 #include "libmesh/static_condensation.h"
15 
16 using namespace libMesh;
17 
18 registerMooseObject("NavierStokesApp", NavierStokesProblem);
19 
22 {
24  params.addClassDescription("A problem that handles Schur complement preconditioning of the "
25  "incompressible Navier-Stokes equations");
26  params.addParam<TagName>(
27  "mass_matrix", "", "The matrix tag name corresponding to the mass matrix.");
28  params.addParam<TagName>(
29  "L_matrix",
30  "",
31  "The matrix tag name corresponding to the diffusive part of the velocity equations.");
32  params.addParam<std::vector<unsigned int>>(
33  "schur_fs_index",
34  {},
35  "if not provided then the top field split is assumed to be the "
36  "Schur split. This is a vector to allow recursive nesting");
37  MooseEnum set_schur_pre("false mass a11_and_mass", "false");
38  params.addParam<MooseEnum>(
39  "set_schur_pre",
40  set_schur_pre,
41  "Whether and what to set as the user-provided Schur complement preconditioner");
42  params.addParam<bool>(
43  "commute_lsc",
44  false,
45  "Whether to use the commuted form of the LSC preconditioner, created by Olshanskii");
46  return params;
47 }
48 
50 #if PETSC_RELEASE_LESS_THAN(3, 20, 0)
51 {
52  mooseError("The preconditioning techniques made available through this class require a PETSc "
53  "version of at least 3.20");
54 }
55 #else
56  ,
57  _commute_lsc(getParam<bool>("commute_lsc")),
58  _mass_matrix(getParam<TagName>("mass_matrix")),
59  _L_matrix(getParam<TagName>("L_matrix")),
61  _have_L_matrix(!_L_matrix.empty()),
62  _set_schur_pre((getParam<MooseEnum>("set_schur_pre").getEnum<SetSchurPreType>())),
63  _schur_fs_index(getParam<std::vector<unsigned int>>("schur_fs_index"))
64 {
65  if (_commute_lsc)
66  {
67  if (!_have_mass_matrix)
68  paramError("mass_matrix",
69  "A pressure mass matrix must be provided if we are commuting the LSC commutator.");
70  if (!_have_L_matrix)
71  paramError("L_matrix",
72  "A matrix corresponding to the viscous component of the momentum equation must be "
73  "provided if we are commuting the LSC commutator.");
74  }
75  else if (_have_L_matrix)
76  paramError("L_matrix",
77  "If not commuting the LSC commutator, then the 'L_matrix' should not be provided "
78  "because it will not be used. For Elman LSC preconditioning, L will be computed "
79  "automatically using system matrix data (e.g. the off-diagonal blocks in the "
80  "velocity-pressure system).");
81 
82  if ((_set_schur_pre != SetSchurPreType::FALSE) && !_have_mass_matrix)
83  paramError(
84  "mass_matrix",
85  "If requesting to use the mass matrix as part of the Schur complement preconditioner via "
86  "'set_schur_pre', then a pressure 'mass_matrix' must be provided");
87 }
88 
90 {
91  auto ierr = (PetscErrorCode)0;
92  if (_Q_scale)
93  {
94  ierr = MatDestroy(&_Q_scale);
95  CHKERRABORT(this->comm().get(), ierr);
96  }
97  if (_L)
98  {
99  ierr = MatDestroy(&_L);
100  CHKERRABORT(this->comm().get(), ierr);
101  }
102 }
103 
104 void
106 {
108  for (const auto & solver_sys : _solver_systems)
109  if (solver_sys->system().has_static_condensation())
110  {
111  if (_have_L_matrix)
112  mooseError("Static condensation and LSC preconditioning not supported together");
113  if (_have_mass_matrix)
114  cast_ref<StaticCondensation &>(solver_sys->getMatrix(massMatrixTagID()))
115  .uncondensed_dofs_only();
116  }
117 }
118 KSP
119 NavierStokesProblem::findSchurKSP(KSP node, const unsigned int tree_position)
120 {
121  auto it = _schur_fs_index.begin() + tree_position;
122  if (it == _schur_fs_index.end())
123  return node;
124 
125  PC fs_pc;
126  PetscInt num_splits;
127  KSP * subksp;
128  KSP next_ksp;
129  IS is;
130  PetscBool is_fs;
131 
132  auto sub_ksp_index = *it;
133 
134  // Get the preconditioner associated with the linear solver
135  LibmeshPetscCall(KSPGetPC(node, &fs_pc));
136 
137  // Verify the preconditioner is a field split preconditioner
138  LibmeshPetscCall(PetscObjectTypeCompare((PetscObject)fs_pc, PCFIELDSPLIT, &is_fs));
139  if (!is_fs)
140  mooseError("Not a field split. Please check the 'schur_fs_index' parameter");
141 
142  // Setup the preconditioner. We need to call this first in order to be able to retrieve the sub
143  // ksps and sub index sets associated with the splits
144  LibmeshPetscCall(PCSetUp(fs_pc));
145 
146  // Get the linear solvers associated with each split
147  LibmeshPetscCall(PCFieldSplitGetSubKSP(fs_pc, &num_splits, &subksp));
148  next_ksp = subksp[sub_ksp_index];
149 
150  // Get the index set for the split at this level of the tree we are traversing to the Schur
151  // complement preconditioner
152  LibmeshPetscCall(PCFieldSplitGetISByIndex(fs_pc, sub_ksp_index, &is));
153 
154  // Store this tree level's index set, which we will eventually use to get the sub-matrices
155  // required for our preconditioning process from the system matrices
156  _index_sets.push_back(is);
157 
158  // Free the array of sub linear solvers that got allocated in the PCFieldSplitGetSubKSP call
159  LibmeshPetscCall(PetscFree(subksp));
160 
161  // Continue traversing down the tree towards the Schur complement linear solver/preconditioner
162  return findSchurKSP(next_ksp, tree_position + 1);
163 }
164 
165 void
167 {
168  KSP * subksp; // This will be length two, with the former being the A KSP and the latter being the
169  // Schur complement KSP
170  KSP schur_complement_ksp;
171  PC schur_pc, lsc_pc;
172  PetscInt num_splits;
173  Mat lsc_pc_pmat;
174  IS velocity_is, pressure_is;
175  PetscInt rstart, rend;
176  PetscBool is_lsc, is_fs;
177  std::vector<Mat> intermediate_Qs;
178  std::vector<Mat> intermediate_Ls;
179 
180  // Get the preconditioner for the linear solver. It must be a field split preconditioner
181  LibmeshPetscCall(KSPGetPC(schur_ksp, &schur_pc));
182  LibmeshPetscCall(PetscObjectTypeCompare((PetscObject)schur_pc, PCFIELDSPLIT, &is_fs));
183  if (!is_fs)
184  mooseError("Not a field split. Please check the 'schur_fs_index' parameter");
185 
186  // The mass matrix. This will correspond to velocity degrees of freedom for Elman LSC and pressure
187  // degrees of freedom for Olshanskii LSC or when directly using the mass matrix as a
188  // preconditioner for the Schur complement
189  Mat global_Q = nullptr;
190  if (_have_mass_matrix)
191  {
192  auto & sparse_mass_mat = _current_nl_sys->getMatrix(massMatrixTagID());
194  global_Q = cast_ref<const PetscMatrixBase<Number> &>(
195  cast_ref<StaticCondensation &>(sparse_mass_mat).get_condensed_mat())
196  .mat();
197  else
198  global_Q = cast_ref<PetscMatrixBase<Number> &>(sparse_mass_mat).mat();
199  }
200 
201  // The Poisson operator matrix corresponding to the velocity degrees of freedom. This is only used
202  // and is required for Olshanskii LSC preconditioning
203  Mat global_L = nullptr;
204  if (_have_L_matrix)
205  global_L =
206  cast_ref<PetscMatrixBase<Number> &>(_current_nl_sys->getMatrix(LMatrixTagID())).mat();
207 
208  //
209  // Process down from our system matrices to the sub-matrix containing the velocity-pressure dofs
210  // for which we are going to be doing the Schur complement preconditioning
211  //
212 
213  auto process_intermediate_mats = [this](auto & intermediate_mats, auto parent_mat)
214  {
215  mooseAssert(parent_mat, "This should be non-null");
216  intermediate_mats.resize(_index_sets.size());
217  for (const auto i : index_range(_index_sets))
218  {
219  auto intermediate_is = _index_sets[i];
220  Mat intermediate_mat;
221  LibmeshPetscCall(MatCreateSubMatrix(i == 0 ? parent_mat : intermediate_mats[i - 1],
222  intermediate_is,
223  intermediate_is,
224  MAT_INITIAL_MATRIX,
225  &intermediate_mat));
226  intermediate_mats[i] = intermediate_mat;
227  }
228  return _index_sets.empty() ? parent_mat : intermediate_mats.back();
229  };
230 
231  Mat our_parent_Q = nullptr;
232  if (_have_mass_matrix)
233  our_parent_Q = process_intermediate_mats(intermediate_Qs, global_Q);
234  Mat our_parent_L = nullptr;
235  if (_have_L_matrix)
236  our_parent_L = process_intermediate_mats(intermediate_Ls, global_L);
237 
238  // Setup the preconditioner. We need to call this first in order to be able to retrieve the sub
239  // ksps and sub index sets associated with the splits
240  LibmeshPetscCall(PCSetUp(schur_pc));
241 
242  // There are always two splits in a Schur complement split. The zeroth split is the split with the
243  // on-diagonals, e.g. the velocity dofs. Here we retrive the velocity dofs/index set
244  LibmeshPetscCall(PCFieldSplitGetISByIndex(schur_pc, 0, &velocity_is));
245 
246  // Get the rows of the parent velocity-pressure matrix that our process owns
247  LibmeshPetscCall(MatGetOwnershipRange(our_parent_Q, &rstart, &rend));
248 
249  if (_commute_lsc)
250  {
251  // If we're commuting LSC, e.g. doing Olshanskii, the user must have provided a Poisson operator
252  // matrix
253  mooseAssert(our_parent_L, "This should be non-null");
254 
255  if (!_L)
256  // If this is our first time in this routine, then we create the matrix
257  LibmeshPetscCall(
258  MatCreateSubMatrix(our_parent_L, velocity_is, velocity_is, MAT_INITIAL_MATRIX, &_L));
259  else
260  // Else we reuse the matrix
261  LibmeshPetscCall(
262  MatCreateSubMatrix(our_parent_L, velocity_is, velocity_is, MAT_REUSE_MATRIX, &_L));
263  }
264 
265  // Get the local index set complement corresponding to the pressure dofs from the velocity dofs
266  LibmeshPetscCall(ISComplement(velocity_is, rstart, rend, &pressure_is));
267 
268  auto create_q_scale_submat =
269  [our_parent_Q, this, velocity_is, pressure_is](const auto & mat_initialization)
270  {
272  {
273  // If we are doing Olshanskii or we are using the pressure matrix directly as the
274  // preconditioner (no LSC), then we must have access to a pressure mass matrix
275  mooseAssert(our_parent_Q, "This should be non-null");
276  // Create a sub-matrix corresponding to the pressure index set
277  LibmeshPetscCall(MatCreateSubMatrix(
278  our_parent_Q, pressure_is, pressure_is, mat_initialization, &_Q_scale));
279  }
280  else if (_have_mass_matrix) // If we don't have a mass matrix and the user has requested scaling
281  // then the diagonal of A will be used
282  {
283  // The user passed us a mass matrix tag; we better have been able to obtain a parent Q in that
284  // case
285  mooseAssert(our_parent_Q, "This should be non-null");
286  // We are not commuting LSC, so we are doing Elman, and the user has passed us a mass matrix
287  // tag. In this case we are creating a velocity mass matrix, so we use the velocity index set
288  LibmeshPetscCall(MatCreateSubMatrix(
289  our_parent_Q, velocity_is, velocity_is, mat_initialization, &_Q_scale));
290  }
291  };
292 
293  if (!_Q_scale)
294  // We haven't allocated the scaling matrix yet
295  create_q_scale_submat(MAT_INITIAL_MATRIX);
296  else
297  {
299  {
300  // A11 has mucked with the nonzero pattern
301  LibmeshPetscCall(MatDestroy(&_Q_scale));
302  create_q_scale_submat(MAT_INITIAL_MATRIX);
303  }
304  else
305  // We have allocated the scaling matrix, so we can reuse
306  create_q_scale_submat(MAT_REUSE_MATRIX);
307  }
308 
309  // We don't need the pressure index set anymore
310  LibmeshPetscCall(ISDestroy(&pressure_is));
311 
312  // Nor the intermediate matrices
313  for (auto & mat : intermediate_Qs)
314  LibmeshPetscCall(MatDestroy(&mat));
315  for (auto & mat : intermediate_Ls)
316  LibmeshPetscCall(MatDestroy(&mat));
317 
318  // Get the sub KSP for the Schur split that corresponds to the linear solver for the Schur
319  // complement (e.g. rank equivalent to the pressure rank)
320  LibmeshPetscCall(PCFieldSplitGetSubKSP(schur_pc, &num_splits, &subksp));
321  if (num_splits != 2)
322  mooseError("The number of splits should be two");
323  // The Schur complement linear solver is always at the first index (for the pressure dofs;
324  // velocity dof KSP is at index 0)
325  schur_complement_ksp = subksp[1];
326 
328  {
329  mooseAssert(_Q_scale, "This should be non-null");
330  Mat S, A11;
331 
332  // Get the Schur complement operator S, which in generic KSP speak is used for the operator A
333  LibmeshPetscCall(KSPGetOperators(schur_complement_ksp, &S, nullptr));
335  {
336  LibmeshPetscCall(
337  MatSchurComplementGetSubMatrices(S, nullptr, nullptr, nullptr, nullptr, &A11));
338  LibmeshPetscCall(MatAXPY(_Q_scale, 1, A11, DIFFERENT_NONZERO_PATTERN));
339  }
340  // Set the Schur complement preconditioner to be the pressure mass matrix
341  LibmeshPetscCall(PCFieldSplitSetSchurPre(schur_pc, PC_FIELDSPLIT_SCHUR_PRE_USER, _Q_scale));
342  // Set, in generic KSP speak, the operators A and P respectively. So our pressure mass matrix is
343  // P
344  LibmeshPetscCall(KSPSetOperators(schur_complement_ksp, S, _Q_scale));
345  }
346  else // We are doing LSC preconditioning
347  {
348  // Get the least squares commutator preconditioner for the Schur complement
349  LibmeshPetscCall(KSPGetPC(schur_complement_ksp, &lsc_pc));
350  // Verify that it's indeed an LSC preconditioner
351  LibmeshPetscCall(PetscObjectTypeCompare(PetscObject(lsc_pc), PCLSC, &is_lsc));
352  if (!is_lsc)
353  mooseError("Not an LSC PC. Please check the 'schur_fs_index' parameter");
354 
355  // Get the LSC preconditioner
356  LibmeshPetscCall(PCGetOperators(lsc_pc, nullptr, &lsc_pc_pmat));
357 
358  if (_commute_lsc)
359  {
360  // We're doing Olshanskii. We must have a user-provided Poisson operator
361  mooseAssert(_L, "This should be non-null");
362  // Attach our L matrix to the PETSc object. PETSc will use this during the preconditioner
363  // application
364  LibmeshPetscCall(PetscObjectCompose((PetscObject)lsc_pc_pmat, "LSC_L", (PetscObject)_L));
365  // Olshanskii preconditioning requires a pressure mass matrix
366  mooseAssert(_have_mass_matrix, "This is to verify we will enter the next conditional");
367  }
368  if (_have_mass_matrix)
369  {
370  mooseAssert(_Q_scale, "This should be non-null");
371  // Attach our scaling/mass matrix to the PETSc object. PETSc will use this during the
372  // preconditioner application
373  LibmeshPetscCall(
374  PetscObjectCompose((PetscObject)lsc_pc_pmat, "LSC_Qscale", (PetscObject)_Q_scale));
375  }
376  }
377 
378  // Free the sub-KSP array that was allocated during PCFieldSplitGetSubKSP
379  LibmeshPetscCall(PetscFree(subksp));
380 }
381 
382 PetscErrorCode
383 navierStokesKSPPreSolve(KSP root_ksp, Vec /*rhs*/, Vec /*x*/, void * context)
384 {
386 
387  auto * ns_problem = static_cast<NavierStokesProblem *>(context);
388  ns_problem->clearIndexSets();
389  auto schur_ksp = ns_problem->findSchurKSP(root_ksp, 0);
390  ns_problem->setupLSCMatrices(schur_ksp);
391 
392  PetscFunctionReturn(PETSC_SUCCESS);
393 }
394 
395 void
397 {
399 
400  if (!_have_mass_matrix)
401  {
402  mooseAssert(
404  "If we don't have a mass matrix, which is only supported for traditional LSC "
405  "preconditioning (e.g. Elman, not Olshanskii), then we also shouldn't have an L matrix "
406  "because we automatically form the L matrix when doing traditional LSC preconditioning");
407  return;
408  }
409 
410  // Set the pre-KSP solve callback. At that time we will setup our Schur complement preconditioning
412  LibmeshPetscCall(KSPSetPreSolve(ksp, &navierStokesKSPPreSolve, this));
413 }
414 
415 #endif
virtual void initPetscOutputAndSomeSolverSettings()
const bool _have_mass_matrix
Whether the user attached a mass matrix.
const std::vector< unsigned int > & _schur_fs_index
The length of this vector should correspond to the number of split nesting levels there are in the fi...
virtual void initialSetup() override
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
Tnew cast_ref(Told &oldvar)
void clearIndexSets()
Clear the field split index sets.
if(subdm)
PetscFunctionBegin
std::vector< IS > _index_sets
This will end up being the same length as _schur_fs_index.
Mat _L
The Poisson operator.
const Parallel::Communicator & comm() const
virtual ~NavierStokesProblem()
Will destroy any matrices we allocated.
std::vector< std::shared_ptr< SolverSystem > > _solver_systems
KSP findSchurKSP(KSP node, unsigned int tree_position)
bool has_static_condensation() const
The following methods are specializations for using the Parallel::packed_range_* routines for a vecto...
static InputParameters validParams()
NonlinearSystemBase * _current_nl_sys
static const std::string S
Definition: NS.h:167
FieldSplitPreconditionerBase & getFieldSplitPreconditioner()
NonlinearSystemBase & currentNonlinearSystem()
NavierStokesProblem(const InputParameters &parameters)
const bool _commute_lsc
Whether to commute operators in the style of Olshanskii.
void initialSetup() override
PetscErrorCode PetscInt const PetscInt IS * is
A problem that handles Schur complement preconditioning of the incompressible Navier-Stokes equations...
virtual void initPetscOutputAndSomeSolverSettings() override
Reinitialize PETSc output for proper linear/nonlinear iteration display.
Mat _Q_scale
The mass matrix used for scaling.
enum NavierStokesProblem::SetSchurPreType _set_schur_pre
TagID LMatrixTagID() const
virtual libMesh::SparseMatrix< Number > & getMatrix(TagID tag)
void setupLSCMatrices(KSP schur_ksp)
Setup the Least Squares Commutator (LSC) preconditioner given the Schur complement KSP object...
void mooseError(Args &&... args) const
void addClassDescription(const std::string &doc_string)
static InputParameters validParams()
TagID massMatrixTagID() const
PetscFunctionReturn(LIBMESH_PETSC_SUCCESS)
const bool _have_L_matrix
Whether the user attached a Poisson operator matrix.
auto index_range(const T &sizable)
virtual libMesh::System & system() override