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  params.addParam<bool>("use_pressure_mass_matrix",
38  false,
39  "Whether to just use the pressure mass matrix as the preconditioner for "
40  "the Schur complement");
41  params.addParam<bool>(
42  "commute_lsc",
43  false,
44  "Whether to use the commuted form of the LSC preconditioner, created by Olshanskii");
45  return params;
46 }
47 
49 #if PETSC_RELEASE_LESS_THAN(3, 20, 0)
50 {
51  mooseError("The preconditioning techniques made available through this class require a PETSc "
52  "version of at least 3.20");
53 }
54 #else
55  ,
56  _commute_lsc(getParam<bool>("commute_lsc")),
57  _mass_matrix(getParam<TagName>("mass_matrix")),
58  _L_matrix(getParam<TagName>("L_matrix")),
60  _have_L_matrix(!_L_matrix.empty()),
61  _pressure_mass_matrix_as_pre(getParam<bool>("use_pressure_mass_matrix")),
62  _schur_fs_index(getParam<std::vector<unsigned int>>("schur_fs_index"))
63 {
64  if (_commute_lsc)
65  {
66  if (!_have_mass_matrix)
67  paramError("mass_matrix",
68  "A pressure mass matrix must be provided if we are commuting the LSC commutator.");
69  if (!_have_L_matrix)
70  paramError("L_matrix",
71  "A matrix corresponding to the viscous component of the momentum equation must be "
72  "provided if we are commuting the LSC commutator.");
73  }
74  else if (_have_L_matrix)
75  paramError("L_matrix",
76  "If not commuting the LSC commutator, then the 'L_matrix' should not be provided "
77  "because it will not be used. For Elman LSC preconditioning, L will be computed "
78  "automatically using system matrix data (e.g. the off-diagonal blocks in the "
79  "velocity-pressure system).");
80 
82  paramError("mass_matrix",
83  "If 'use_pressure_mass_matrix', then a pressure 'mass_matrix' must be provided");
84 }
85 
87 {
88  auto ierr = (PetscErrorCode)0;
89  if (_Q_scale)
90  {
91  ierr = MatDestroy(&_Q_scale);
92  CHKERRABORT(this->comm().get(), ierr);
93  }
94  if (_L)
95  {
96  ierr = MatDestroy(&_L);
97  CHKERRABORT(this->comm().get(), ierr);
98  }
99 }
100 
101 void
103 {
105  for (const auto & solver_sys : _solver_systems)
106  if (solver_sys->system().has_static_condensation())
107  {
108  if (_have_L_matrix)
109  mooseError("Static condensation and LSC preconditioning not supported together");
110  if (_have_mass_matrix)
111  cast_ref<StaticCondensation &>(solver_sys->getMatrix(massMatrixTagID()))
112  .uncondensed_dofs_only();
113  }
114 }
115 KSP
116 NavierStokesProblem::findSchurKSP(KSP node, const unsigned int tree_position)
117 {
118  auto it = _schur_fs_index.begin() + tree_position;
119  if (it == _schur_fs_index.end())
120  return node;
121 
122  PC fs_pc;
123  PetscInt num_splits;
124  KSP * subksp;
125  KSP next_ksp;
126  IS is;
127  PetscBool is_fs;
128 
129  auto sub_ksp_index = *it;
130 
131  // Get the preconditioner associated with the linear solver
132  LibmeshPetscCall(KSPGetPC(node, &fs_pc));
133 
134  // Verify the preconditioner is a field split preconditioner
135  LibmeshPetscCall(PetscObjectTypeCompare((PetscObject)fs_pc, PCFIELDSPLIT, &is_fs));
136  if (!is_fs)
137  mooseError("Not a field split. Please check the 'schur_fs_index' parameter");
138 
139  // Setup the preconditioner. We need to call this first in order to be able to retrieve the sub
140  // ksps and sub index sets associated with the splits
141  LibmeshPetscCall(PCSetUp(fs_pc));
142 
143  // Get the linear solvers associated with each split
144  LibmeshPetscCall(PCFieldSplitGetSubKSP(fs_pc, &num_splits, &subksp));
145  next_ksp = subksp[sub_ksp_index];
146 
147  // Get the index set for the split at this level of the tree we are traversing to the Schur
148  // complement preconditioner
149  LibmeshPetscCall(PCFieldSplitGetISByIndex(fs_pc, sub_ksp_index, &is));
150 
151  // Store this tree level's index set, which we will eventually use to get the sub-matrices
152  // required for our preconditioning process from the system matrices
153  _index_sets.push_back(is);
154 
155  // Free the array of sub linear solvers that got allocated in the PCFieldSplitGetSubKSP call
156  LibmeshPetscCall(PetscFree(subksp));
157 
158  // Continue traversing down the tree towards the Schur complement linear solver/preconditioner
159  return findSchurKSP(next_ksp, tree_position + 1);
160 }
161 
162 void
164 {
165  KSP * subksp; // This will be length two, with the former being the A KSP and the latter being the
166  // Schur complement KSP
167  KSP schur_complement_ksp;
168  PC schur_pc, lsc_pc;
169  PetscInt num_splits;
170  Mat lsc_pc_pmat;
171  IS velocity_is, pressure_is;
172  PetscInt rstart, rend;
173  PetscBool is_lsc, is_fs;
174  std::vector<Mat> intermediate_Qs;
175  std::vector<Mat> intermediate_Ls;
176 
177  // Get the preconditioner for the linear solver. It must be a field split preconditioner
178  LibmeshPetscCall(KSPGetPC(schur_ksp, &schur_pc));
179  LibmeshPetscCall(PetscObjectTypeCompare((PetscObject)schur_pc, PCFIELDSPLIT, &is_fs));
180  if (!is_fs)
181  mooseError("Not a field split. Please check the 'schur_fs_index' parameter");
182 
183  // The mass matrix. This will correspond to velocity degrees of freedom for Elman LSC and pressure
184  // degrees of freedom for Olshanskii LSC or when directly using the mass matrix as a
185  // preconditioner for the Schur complement
186  Mat global_Q = nullptr;
187  if (_have_mass_matrix)
188  {
189  auto & sparse_mass_mat = _current_nl_sys->getMatrix(massMatrixTagID());
191  global_Q = cast_ref<const PetscMatrixBase<Number> &>(
192  cast_ref<StaticCondensation &>(sparse_mass_mat).get_condensed_mat())
193  .mat();
194  else
195  global_Q = cast_ref<PetscMatrixBase<Number> &>(sparse_mass_mat).mat();
196  }
197 
198  // The Poisson operator matrix corresponding to the velocity degrees of freedom. This is only used
199  // and is required for Olshanskii LSC preconditioning
200  Mat global_L = nullptr;
201  if (_have_L_matrix)
202  global_L =
203  cast_ref<PetscMatrixBase<Number> &>(_current_nl_sys->getMatrix(LMatrixTagID())).mat();
204 
205  //
206  // Process down from our system matrices to the sub-matrix containing the velocity-pressure dofs
207  // for which we are going to be doing the Schur complement preconditioning
208  //
209 
210  auto process_intermediate_mats = [this](auto & intermediate_mats, auto parent_mat)
211  {
212  mooseAssert(parent_mat, "This should be non-null");
213  intermediate_mats.resize(_index_sets.size());
214  for (const auto i : index_range(_index_sets))
215  {
216  auto intermediate_is = _index_sets[i];
217  Mat intermediate_mat;
218  LibmeshPetscCall(MatCreateSubMatrix(i == 0 ? parent_mat : intermediate_mats[i - 1],
219  intermediate_is,
220  intermediate_is,
221  MAT_INITIAL_MATRIX,
222  &intermediate_mat));
223  intermediate_mats[i] = intermediate_mat;
224  }
225  return _index_sets.empty() ? parent_mat : intermediate_mats.back();
226  };
227 
228  Mat our_parent_Q = nullptr;
229  if (_have_mass_matrix)
230  our_parent_Q = process_intermediate_mats(intermediate_Qs, global_Q);
231  Mat our_parent_L = nullptr;
232  if (_have_L_matrix)
233  our_parent_L = process_intermediate_mats(intermediate_Ls, global_L);
234 
235  // Setup the preconditioner. We need to call this first in order to be able to retrieve the sub
236  // ksps and sub index sets associated with the splits
237  LibmeshPetscCall(PCSetUp(schur_pc));
238 
239  // There are always two splits in a Schur complement split. The zeroth split is the split with the
240  // on-diagonals, e.g. the velocity dofs. Here we retrive the velocity dofs/index set
241  LibmeshPetscCall(PCFieldSplitGetISByIndex(schur_pc, 0, &velocity_is));
242 
243  // Get the rows of the parent velocity-pressure matrix that our process owns
244  LibmeshPetscCall(MatGetOwnershipRange(our_parent_Q, &rstart, &rend));
245 
246  if (_commute_lsc)
247  {
248  // If we're commuting LSC, e.g. doing Olshanskii, the user must have provided a Poisson operator
249  // matrix
250  mooseAssert(our_parent_L, "This should be non-null");
251 
252  if (!_L)
253  // If this is our first time in this routine, then we create the matrix
254  LibmeshPetscCall(
255  MatCreateSubMatrix(our_parent_L, velocity_is, velocity_is, MAT_INITIAL_MATRIX, &_L));
256  else
257  // Else we reuse the matrix
258  LibmeshPetscCall(
259  MatCreateSubMatrix(our_parent_L, velocity_is, velocity_is, MAT_REUSE_MATRIX, &_L));
260  }
261 
262  // Get the local index set complement corresponding to the pressure dofs from the velocity dofs
263  LibmeshPetscCall(ISComplement(velocity_is, rstart, rend, &pressure_is));
264 
265  auto create_q_scale_submat =
266  [our_parent_Q, this, velocity_is, pressure_is](const auto & mat_initialization)
267  {
269  {
270  // If we are doing Olshanskii or we are using the pressure matrix directly as the
271  // preconditioner (no LSC), then we must have access to a pressure mass matrix
272  mooseAssert(our_parent_Q, "This should be non-null");
273  // Create a sub-matrix corresponding to the pressure index set
274  LibmeshPetscCall(MatCreateSubMatrix(
275  our_parent_Q, pressure_is, pressure_is, mat_initialization, &_Q_scale));
276  }
277  else if (_have_mass_matrix) // If we don't have a mass matrix and the user has requested scaling
278  // then the diagonal of A will be used
279  {
280  // The user passed us a mass matrix tag; we better have been able to obtain a parent Q in that
281  // case
282  mooseAssert(our_parent_Q, "This should be non-null");
283  // We are not commuting LSC, so we are doing Elman, and the user has passed us a mass matrix
284  // tag. In this case we are creating a velocity mass matrix, so we use the velocity index set
285  LibmeshPetscCall(MatCreateSubMatrix(
286  our_parent_Q, velocity_is, velocity_is, mat_initialization, &_Q_scale));
287  }
288  };
289 
290  if (!_Q_scale)
291  // We haven't allocated the scaling matrix yet
292  create_q_scale_submat(MAT_INITIAL_MATRIX);
293  else
294  // We have allocated the scaling matrix, so we can reuse
295  create_q_scale_submat(MAT_REUSE_MATRIX);
296 
297  // We don't need the pressure index set anymore
298  LibmeshPetscCall(ISDestroy(&pressure_is));
299 
300  // Nor the intermediate matrices
301  for (auto & mat : intermediate_Qs)
302  LibmeshPetscCall(MatDestroy(&mat));
303  for (auto & mat : intermediate_Ls)
304  LibmeshPetscCall(MatDestroy(&mat));
305 
306  // Get the sub KSP for the Schur split that corresponds to the linear solver for the Schur
307  // complement (e.g. rank equivalent to the pressure rank)
308  LibmeshPetscCall(PCFieldSplitGetSubKSP(schur_pc, &num_splits, &subksp));
309  if (num_splits != 2)
310  mooseError("The number of splits should be two");
311  // The Schur complement linear solver is always at the first index (for the pressure dofs;
312  // velocity dof KSP is at index 0)
313  schur_complement_ksp = subksp[1];
314 
316  {
317  mooseAssert(_Q_scale, "This should be non-null");
318  Mat S;
319  // Set the Schur complement preconditioner to be the pressure mass matrix
320  LibmeshPetscCall(PCFieldSplitSetSchurPre(schur_pc, PC_FIELDSPLIT_SCHUR_PRE_USER, _Q_scale));
321  // Get the Schur complement operator S, which in generic KSP speak is used for the operator A
322  LibmeshPetscCall(KSPGetOperators(schur_complement_ksp, &S, NULL));
323  // Set, in generic KSP speak, the operators A and P respectively. So our pressure mass matrix is
324  // P
325  LibmeshPetscCall(KSPSetOperators(schur_complement_ksp, S, _Q_scale));
326  }
327  else // We are doing LSC preconditioning
328  {
329  // Get the least squares commutator preconditioner for the Schur complement
330  LibmeshPetscCall(KSPGetPC(schur_complement_ksp, &lsc_pc));
331  // Verify that it's indeed an LSC preconditioner
332  LibmeshPetscCall(PetscObjectTypeCompare(PetscObject(lsc_pc), PCLSC, &is_lsc));
333  if (!is_lsc)
334  mooseError("Not an LSC PC. Please check the 'schur_fs_index' parameter");
335 
336  // Get the LSC preconditioner
337  LibmeshPetscCall(PCGetOperators(lsc_pc, NULL, &lsc_pc_pmat));
338 
339  if (_commute_lsc)
340  {
341  // We're doing Olshanskii. We must have a user-provided Poisson operator
342  mooseAssert(_L, "This should be non-null");
343  // Attach our L matrix to the PETSc object. PETSc will use this during the preconditioner
344  // application
345  LibmeshPetscCall(PetscObjectCompose((PetscObject)lsc_pc_pmat, "LSC_L", (PetscObject)_L));
346  // Olshanskii preconditioning requires a pressure mass matrix
347  mooseAssert(_have_mass_matrix, "This is to verify we will enter the next conditional");
348  }
349  if (_have_mass_matrix)
350  {
351  mooseAssert(_Q_scale, "This should be non-null");
352  // Attach our scaling/mass matrix to the PETSc object. PETSc will use this during the
353  // preconditioner application
354  LibmeshPetscCall(
355  PetscObjectCompose((PetscObject)lsc_pc_pmat, "LSC_Qscale", (PetscObject)_Q_scale));
356  }
357  }
358 
359  // Free the sub-KSP array that was allocated during PCFieldSplitGetSubKSP
360  LibmeshPetscCall(PetscFree(subksp));
361 }
362 
363 PetscErrorCode
364 navierStokesKSPPreSolve(KSP root_ksp, Vec /*rhs*/, Vec /*x*/, void * context)
365 {
367 
368  auto * ns_problem = static_cast<NavierStokesProblem *>(context);
369  ns_problem->clearIndexSets();
370  auto schur_ksp = ns_problem->findSchurKSP(root_ksp, 0);
371  ns_problem->setupLSCMatrices(schur_ksp);
372 
373  PetscFunctionReturn(PETSC_SUCCESS);
374 }
375 
376 void
378 {
380 
381  if (!_have_mass_matrix)
382  {
383  mooseAssert(
385  "If we don't have a mass matrix, which is only supported for traditional LSC "
386  "preconditioning (e.g. Elman, not Olshanskii), then we also shouldn't have an L matrix "
387  "because we automatically form the L matrix when doing traditional LSC preconditioning");
388  return;
389  }
390 
391  // Set the pre-KSP solve callback. At that time we will setup our Schur complement preconditioning
393  LibmeshPetscCall(KSPSetPreSolve(ksp, &navierStokesKSPPreSolve, this));
394 }
395 
396 #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:163
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...
const bool _pressure_mass_matrix_as_pre
Whether to directly use the pressure mass matrix to form the Schur complement preconditioner.
virtual void initPetscOutputAndSomeSolverSettings() override
Reinitialize PETSc output for proper linear/nonlinear iteration display.
Mat _Q_scale
The mass matrix used for scaling.
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