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