https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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
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 MooseEnum set_schur_pre("false mass a11_and_mass", "false");
36 params.addParam<MooseEnum>(
37 "set_schur_pre",
38 set_schur_pre,
39 "Whether and what to set as the user-provided Schur complement preconditioner");
40 params.addParam<bool>(
41 "commute_lsc",
42 false,
43 "Whether to use the commuted form of the LSC preconditioner, created by Olshanskii");
44 return params;
45}
46
48#if PETSC_RELEASE_LESS_THAN(3, 20, 0)
49{
50 mooseError("The preconditioning techniques made available through this class require a PETSc "
51 "version of at least 3.20");
52}
53#else
54 ,
55 _commute_lsc(getParam<bool>("commute_lsc")),
56 _mass_matrix(getParam<TagName>("mass_matrix")),
57 _L_matrix(getParam<TagName>("L_matrix")),
60 _set_schur_pre((getParam<MooseEnum>("set_schur_pre").getEnum<SetSchurPreType>())),
61 _schur_fs_index(getParam<std::vector<unsigned int>>("schur_fs_index")),
64{
65 for (const auto tree_position : index_range(_field_split_post_setup_contexts))
66 _field_split_post_setup_contexts[tree_position] = {this, tree_position};
67
68 if (_commute_lsc)
69 {
71 paramError("mass_matrix",
72 "A pressure mass matrix must be provided if we are commuting the LSC commutator.");
73 if (!_have_L_matrix)
74 paramError("L_matrix",
75 "A matrix corresponding to the viscous component of the momentum equation must be "
76 "provided if we are commuting the LSC commutator.");
77 }
78 else if (_have_L_matrix)
79 paramError("L_matrix",
80 "If not commuting the LSC commutator, then the 'L_matrix' should not be provided "
81 "because it will not be used. For Elman LSC preconditioning, L will be computed "
82 "automatically using system matrix data (e.g. the off-diagonal blocks in the "
83 "velocity-pressure system).");
84
85 if ((_set_schur_pre != SetSchurPreType::FALSE) && !_have_mass_matrix)
86 paramError(
87 "mass_matrix",
88 "If requesting to use the mass matrix as part of the Schur complement preconditioner via "
89 "'set_schur_pre', then a pressure 'mass_matrix' must be provided");
90}
91
93{
94 auto ierr = (PetscErrorCode)0;
95 if (_Q_scale)
96 {
97 ierr = MatDestroy(&_Q_scale);
98 CHKERRABORT(this->comm().get(), ierr);
99 }
100 if (_L)
101 {
102 ierr = MatDestroy(&_L);
103 CHKERRABORT(this->comm().get(), ierr);
104 }
105}
106
107void
109{
111 for (const auto & solver_sys : _solver_systems)
112 if (solver_sys->system().has_static_condensation())
113 {
114 if (_have_L_matrix)
115 mooseError("Static condensation and LSC preconditioning not supported together");
117 cast_ref<libMesh::StaticCondensation &>(solver_sys->getMatrix(massMatrixTagID()))
118 .uncondensed_dofs_only();
119 }
120}
121PetscErrorCode
123{
125
126 void * context;
127 PetscCall(PCGetApplicationContext(pc, &context));
128 const auto & post_setup_context = *static_cast<FieldSplitPostSetUpContext *>(context);
129 post_setup_context.problem->fieldSplitPostSetUp(pc, post_setup_context.tree_position);
130
131 PetscFunctionReturn(PETSC_SUCCESS);
132}
133
134void
135NavierStokesProblem::setFieldSplitPostSetUp(PC pc, const std::size_t tree_position)
136{
137 LibmeshPetscCall(PCSetApplicationContext(pc, &_field_split_post_setup_contexts[tree_position]));
138 LibmeshPetscCall(PCSetPostSetUp(pc, &NavierStokesProblem::fieldSplitPostSetUpCallback));
139}
140
141void
142NavierStokesProblem::fieldSplitPostSetUp(PC pc, const std::size_t tree_position)
143{
144 PetscBool is_fs;
145 LibmeshPetscCall(PetscObjectTypeCompare((PetscObject)pc, PCFIELDSPLIT, &is_fs));
146 if (!is_fs)
147 mooseError("Not a field split. Please check the 'schur_fs_index' parameter");
148
149 if (tree_position == _schur_fs_index.size())
150 {
152 return;
153 }
154
155 PetscInt num_splits;
156 KSP * subksp;
157 IS is;
158 PC next_pc;
159 const auto sub_ksp_index = _schur_fs_index[tree_position];
160
161 // Get the linear solvers associated with each split
162 LibmeshPetscCall(PCFieldSplitGetSubKSP(pc, &num_splits, &subksp));
163 LibmeshPetscCall(KSPGetPC(subksp[sub_ksp_index], &next_pc));
164
165 // Get the index set for the split at this level of the tree we are traversing to the Schur
166 // complement preconditioner
167 LibmeshPetscCall(PCFieldSplitGetISByIndex(pc, sub_ksp_index, &is));
168
169 // Store this tree level's index set, which we will eventually use to get the sub-matrices
170 // required for our preconditioning process from the system matrices
171 _index_sets[tree_position] = is;
172
173 // PETSc will set up the child while recursively setting up the field split blocks
174 setFieldSplitPostSetUp(next_pc, tree_position + 1);
175
176 // Free the array of sub linear solvers that got allocated in the PCFieldSplitGetSubKSP call
177 LibmeshPetscCall(PetscFree(subksp));
178}
179
180void
182{
183 KSP * subksp; // This will be length two, with the former being the A KSP and the latter being the
184 // Schur complement KSP
185 KSP schur_complement_ksp;
186 PC lsc_pc;
187 PetscInt num_splits;
188 Mat lsc_pc_pmat;
189 IS velocity_is, pressure_is;
190 PetscInt rstart, rend;
191 PetscBool is_lsc;
192 std::vector<Mat> intermediate_Qs;
193 std::vector<Mat> intermediate_Ls;
194
195 // The mass matrix. This will correspond to velocity degrees of freedom for Elman LSC and pressure
196 // degrees of freedom for Olshanskii LSC or when directly using the mass matrix as a
197 // preconditioner for the Schur complement
198 Mat global_Q = nullptr;
200 {
201 auto & sparse_mass_mat = _current_nl_sys->getMatrix(massMatrixTagID());
203 global_Q = cast_ref<const libMesh::PetscMatrixBase<Number> &>(
204 cast_ref<libMesh::StaticCondensation &>(sparse_mass_mat).get_condensed_mat())
205 .mat();
206 else
207 global_Q = cast_ref<libMesh::PetscMatrixBase<Number> &>(sparse_mass_mat).mat();
208 }
209
210 // The Poisson operator matrix corresponding to the velocity degrees of freedom. This is only used
211 // and is required for Olshanskii LSC preconditioning
212 Mat global_L = nullptr;
213 if (_have_L_matrix)
214 global_L =
215 cast_ref<libMesh::PetscMatrixBase<Number> &>(_current_nl_sys->getMatrix(LMatrixTagID()))
216 .mat();
217
218 //
219 // Process down from our system matrices to the sub-matrix containing the velocity-pressure dofs
220 // for which we are going to be doing the Schur complement preconditioning
221 //
222
223 auto process_intermediate_mats = [this](auto & intermediate_mats, auto parent_mat)
224 {
225 mooseAssert(parent_mat, "This should be non-null");
226 intermediate_mats.resize(_index_sets.size());
227 for (const auto i : index_range(_index_sets))
228 {
229 auto intermediate_is = _index_sets[i];
230 Mat intermediate_mat;
231 LibmeshPetscCall(MatCreateSubMatrix(i == 0 ? parent_mat : intermediate_mats[i - 1],
232 intermediate_is,
233 intermediate_is,
234 MAT_INITIAL_MATRIX,
235 &intermediate_mat));
236 intermediate_mats[i] = intermediate_mat;
237 }
238 return _index_sets.empty() ? parent_mat : intermediate_mats.back();
239 };
240
241 Mat our_parent_Q = nullptr;
243 our_parent_Q = process_intermediate_mats(intermediate_Qs, global_Q);
244 Mat our_parent_L = nullptr;
245 if (_have_L_matrix)
246 our_parent_L = process_intermediate_mats(intermediate_Ls, global_L);
247
248 // There are always two splits in a Schur complement split. The zeroth split is the split with the
249 // on-diagonals, e.g. the velocity dofs. Here we retrive the velocity dofs/index set
250 LibmeshPetscCall(PCFieldSplitGetISByIndex(schur_pc, 0, &velocity_is));
251
252 // Get the rows of the parent velocity-pressure matrix that our process owns
253 LibmeshPetscCall(MatGetOwnershipRange(our_parent_Q, &rstart, &rend));
254
255 if (_commute_lsc)
256 {
257 // If we're commuting LSC, e.g. doing Olshanskii, the user must have provided a Poisson operator
258 // matrix
259 mooseAssert(our_parent_L, "This should be non-null");
260
261 if (!_L)
262 // If this is our first time in this routine, then we create the matrix
263 LibmeshPetscCall(
264 MatCreateSubMatrix(our_parent_L, velocity_is, velocity_is, MAT_INITIAL_MATRIX, &_L));
265 else
266 // Else we reuse the matrix
267 LibmeshPetscCall(
268 MatCreateSubMatrix(our_parent_L, velocity_is, velocity_is, MAT_REUSE_MATRIX, &_L));
269 }
270
271 // Get the local index set complement corresponding to the pressure dofs from the velocity dofs
272 LibmeshPetscCall(ISComplement(velocity_is, rstart, rend, &pressure_is));
273
274 auto create_q_scale_submat =
275 [our_parent_Q, this, velocity_is, pressure_is](const auto & mat_initialization)
276 {
278 {
279 // If we are doing Olshanskii or we are using the pressure matrix directly as the
280 // preconditioner (no LSC), then we must have access to a pressure mass matrix
281 mooseAssert(our_parent_Q, "This should be non-null");
282 // Create a sub-matrix corresponding to the pressure index set
283 LibmeshPetscCall(MatCreateSubMatrix(
284 our_parent_Q, pressure_is, pressure_is, mat_initialization, &_Q_scale));
285 }
286 else if (_have_mass_matrix) // If we don't have a mass matrix and the user has requested scaling
287 // then the diagonal of A will be used
288 {
289 // The user passed us a mass matrix tag; we better have been able to obtain a parent Q in that
290 // case
291 mooseAssert(our_parent_Q, "This should be non-null");
292 // We are not commuting LSC, so we are doing Elman, and the user has passed us a mass matrix
293 // tag. In this case we are creating a velocity mass matrix, so we use the velocity index set
294 LibmeshPetscCall(MatCreateSubMatrix(
295 our_parent_Q, velocity_is, velocity_is, mat_initialization, &_Q_scale));
296 }
297 };
298
299 if (!_Q_scale)
300 // We haven't allocated the scaling matrix yet
301 create_q_scale_submat(MAT_INITIAL_MATRIX);
302 else
303 {
305 {
306 // A11 has mucked with the nonzero pattern
307 LibmeshPetscCall(MatDestroy(&_Q_scale));
308 create_q_scale_submat(MAT_INITIAL_MATRIX);
309 }
310 else
311 // We have allocated the scaling matrix, so we can reuse
312 create_q_scale_submat(MAT_REUSE_MATRIX);
313 }
314
315 // We don't need the pressure index set anymore
316 LibmeshPetscCall(ISDestroy(&pressure_is));
317
318 // Nor the intermediate matrices
319 for (auto & mat : intermediate_Qs)
320 LibmeshPetscCall(MatDestroy(&mat));
321 for (auto & mat : intermediate_Ls)
322 LibmeshPetscCall(MatDestroy(&mat));
323
324 // Get the sub KSP for the Schur split that corresponds to the linear solver for the Schur
325 // complement (e.g. rank equivalent to the pressure rank)
326 LibmeshPetscCall(PCFieldSplitGetSubKSP(schur_pc, &num_splits, &subksp));
327 if (num_splits != 2)
328 mooseError("The number of splits should be two");
329 // The Schur complement linear solver is always at the first index (for the pressure dofs;
330 // velocity dof KSP is at index 0)
331 schur_complement_ksp = subksp[1];
332
334 {
335 mooseAssert(_Q_scale, "This should be non-null");
336 Mat S, A11;
337
338 // Get the Schur complement operator S, which in generic KSP speak is used for the operator A
339 LibmeshPetscCall(KSPGetOperators(schur_complement_ksp, &S, nullptr));
341 {
342 LibmeshPetscCall(
343 MatSchurComplementGetSubMatrices(S, nullptr, nullptr, nullptr, nullptr, &A11));
344 LibmeshPetscCall(MatAXPY(_Q_scale, 1, A11, DIFFERENT_NONZERO_PATTERN));
345 }
346 // Set the Schur complement preconditioner to be the pressure mass matrix
347 LibmeshPetscCall(PCFieldSplitSetSchurPre(schur_pc, PC_FIELDSPLIT_SCHUR_PRE_USER, _Q_scale));
348 // Set, in generic KSP speak, the operators A and P respectively. So our pressure mass matrix is
349 // P
350 LibmeshPetscCall(KSPSetOperators(schur_complement_ksp, S, _Q_scale));
351 }
352 else // We are doing LSC preconditioning
353 {
354 // Get the least squares commutator preconditioner for the Schur complement
355 LibmeshPetscCall(KSPGetPC(schur_complement_ksp, &lsc_pc));
356 // Verify that it's indeed an LSC preconditioner
357 LibmeshPetscCall(PetscObjectTypeCompare(PetscObject(lsc_pc), PCLSC, &is_lsc));
358 if (!is_lsc)
359 mooseError("Not an LSC PC. Please check the 'schur_fs_index' parameter");
360
361 // Get the LSC preconditioner
362 LibmeshPetscCall(PCGetOperators(lsc_pc, nullptr, &lsc_pc_pmat));
363
364 if (_commute_lsc)
365 {
366 // We're doing Olshanskii. We must have a user-provided Poisson operator
367 mooseAssert(_L, "This should be non-null");
368 // Attach our L matrix to the PETSc object. PETSc will use this during the preconditioner
369 // application
370 LibmeshPetscCall(PetscObjectCompose((PetscObject)lsc_pc_pmat, "LSC_L", (PetscObject)_L));
371 // Olshanskii preconditioning requires a pressure mass matrix
372 mooseAssert(_have_mass_matrix, "This is to verify we will enter the next conditional");
373 }
375 {
376 mooseAssert(_Q_scale, "This should be non-null");
377 // Attach our scaling/mass matrix to the PETSc object. PETSc will use this during the
378 // preconditioner application
379 LibmeshPetscCall(
380 PetscObjectCompose((PetscObject)lsc_pc_pmat, "LSC_Qscale", (PetscObject)_Q_scale));
381 }
382 }
383
384 // Free the sub-KSP array that was allocated during PCFieldSplitGetSubKSP
385 LibmeshPetscCall(PetscFree(subksp));
386}
387
388void
390{
392
394 {
395 mooseAssert(
397 "If we don't have a mass matrix, which is only supported for traditional LSC "
398 "preconditioning (e.g. Elman, not Olshanskii), then we also shouldn't have an L matrix "
399 "because we automatically form the L matrix when doing traditional LSC preconditioning");
400 return;
401 }
402
403 // Each field split callback will install the callback on the next nested split until PETSc sets
404 // up the target Schur complement split
406 PC pc;
407 LibmeshPetscCall(KSPGetPC(ksp, &pc));
409}
410
411#endif
registerMooseObject("NavierStokesApp", NavierStokesProblem)
_have_L_matrix(!_L_matrix.empty())
_commute_lsc(getParam< bool >("commute_lsc"))
_mass_matrix(getParam< TagName >("mass_matrix"))
_set_schur_pre((getParam< MooseEnum >("set_schur_pre").getEnum< SetSchurPreType >()))
_field_split_post_setup_contexts(_schur_fs_index.size()+1)
_index_sets(_schur_fs_index.size())
_schur_fs_index(getParam< std::vector< unsigned int > >("schur_fs_index"))
_have_mass_matrix(!_mass_matrix.empty())
_L_matrix(getParam< TagName >("L_matrix"))
PetscFunctionBegin
NonlinearSystemBase & currentNonlinearSystem()
NonlinearSystemBase * _current_nl_sys
virtual void initPetscOutputAndSomeSolverSettings()
void initialSetup() override
std::vector< std::shared_ptr< SolverSystem > > _solver_systems
static InputParameters validParams()
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
void mooseError(Args &&... args) const
A problem that handles Schur complement preconditioning of the incompressible Navier-Stokes equations...
void setupLSCMatrices(PC schur_pc)
Set up the Least Squares Commutator (LSC) preconditioner for the Schur complement.
TagID massMatrixTagID() const
virtual void initialSetup() override
std::vector< IS > _index_sets
This will end up being the same length as _schur_fs_index.
void setFieldSplitPostSetUp(PC pc, std::size_t tree_position)
Install the post-setup callback and its context on a field split PC.
static PetscErrorCode fieldSplitPostSetUpCallback(PC pc)
Run after PETSc sets up a field split in the tree leading to the Schur complement.
const bool _commute_lsc
Whether to commute operators in the style of Olshanskii.
Mat _L
The Poisson operator.
enum NavierStokesProblem::SetSchurPreType _set_schur_pre
Mat _Q_scale
The mass matrix used for scaling.
const bool _have_L_matrix
Whether the user attached a Poisson operator matrix.
static InputParameters validParams()
const bool _have_mass_matrix
Whether the user attached a mass matrix.
std::vector< FieldSplitPostSetUpContext > _field_split_post_setup_contexts
Stable callback contexts for each level of the field split tree and its terminal node.
virtual ~NavierStokesProblem()
Will destroy any matrices we allocated.
virtual void initPetscOutputAndSomeSolverSettings() override
Reinitialize PETSc output for proper linear/nonlinear iteration display.
void fieldSplitPostSetUp(PC pc, std::size_t tree_position)
Continue through the field split tree or set up the target Schur complement matrices.
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...
NavierStokesProblem(const InputParameters &parameters)
FieldSplitPreconditionerBase & getFieldSplitPreconditioner()
virtual libMesh::System & system() override
virtual libMesh::SparseMatrix< Number > & getMatrix(TagID tag)
const Parallel::Communicator & comm() const
bool has_static_condensation() const
Context object attached to a field split PC via PCSetApplicationContext so that the static fieldSplit...
NavierStokesProblem * problem
The problem that owns the field split tree being set up.