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
16using namespace libMesh;
17
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")),
62 _set_schur_pre((getParam<MooseEnum>("set_schur_pre").getEnum<SetSchurPreType>())),
63 _schur_fs_index(getParam<std::vector<unsigned int>>("schur_fs_index")),
66{
67 for (const auto tree_position : index_range(_field_split_post_setup_contexts))
68 _field_split_post_setup_contexts[tree_position] = {this, tree_position};
69
70 if (_commute_lsc)
71 {
73 paramError("mass_matrix",
74 "A pressure mass matrix must be provided if we are commuting the LSC commutator.");
75 if (!_have_L_matrix)
76 paramError("L_matrix",
77 "A matrix corresponding to the viscous component of the momentum equation must be "
78 "provided if we are commuting the LSC commutator.");
79 }
80 else if (_have_L_matrix)
81 paramError("L_matrix",
82 "If not commuting the LSC commutator, then the 'L_matrix' should not be provided "
83 "because it will not be used. For Elman LSC preconditioning, L will be computed "
84 "automatically using system matrix data (e.g. the off-diagonal blocks in the "
85 "velocity-pressure system).");
86
87 if ((_set_schur_pre != SetSchurPreType::FALSE) && !_have_mass_matrix)
88 paramError(
89 "mass_matrix",
90 "If requesting to use the mass matrix as part of the Schur complement preconditioner via "
91 "'set_schur_pre', then a pressure 'mass_matrix' must be provided");
92}
93
95{
96 auto ierr = (PetscErrorCode)0;
97 if (_Q_scale)
98 {
99 ierr = MatDestroy(&_Q_scale);
100 CHKERRABORT(this->comm().get(), ierr);
101 }
102 if (_L)
103 {
104 ierr = MatDestroy(&_L);
105 CHKERRABORT(this->comm().get(), ierr);
106 }
107}
108
109void
111{
113 for (const auto & solver_sys : _solver_systems)
114 if (solver_sys->system().has_static_condensation())
115 {
116 if (_have_L_matrix)
117 mooseError("Static condensation and LSC preconditioning not supported together");
119 cast_ref<StaticCondensation &>(solver_sys->getMatrix(massMatrixTagID()))
120 .uncondensed_dofs_only();
121 }
122}
123PetscErrorCode
125{
127
128 void * context;
129 PetscCall(PCGetApplicationContext(pc, &context));
130 const auto & post_setup_context = *static_cast<FieldSplitPostSetUpContext *>(context);
131 post_setup_context.problem->fieldSplitPostSetUp(pc, post_setup_context.tree_position);
132
133 PetscFunctionReturn(PETSC_SUCCESS);
134}
135
136void
137NavierStokesProblem::setFieldSplitPostSetUp(PC pc, const std::size_t tree_position)
138{
139 LibmeshPetscCall(PCSetApplicationContext(pc, &_field_split_post_setup_contexts[tree_position]));
140 LibmeshPetscCall(PCSetPostSetUp(pc, &NavierStokesProblem::fieldSplitPostSetUpCallback));
141}
142
143void
144NavierStokesProblem::fieldSplitPostSetUp(PC pc, const std::size_t tree_position)
145{
146 PetscBool is_fs;
147 LibmeshPetscCall(PetscObjectTypeCompare((PetscObject)pc, PCFIELDSPLIT, &is_fs));
148 if (!is_fs)
149 mooseError("Not a field split. Please check the 'schur_fs_index' parameter");
150
151 if (tree_position == _schur_fs_index.size())
152 {
154 return;
155 }
156
157 PetscInt num_splits;
158 KSP * subksp;
159 IS is;
160 PC next_pc;
161 const auto sub_ksp_index = _schur_fs_index[tree_position];
162
163 // Get the linear solvers associated with each split
164 LibmeshPetscCall(PCFieldSplitGetSubKSP(pc, &num_splits, &subksp));
165 LibmeshPetscCall(KSPGetPC(subksp[sub_ksp_index], &next_pc));
166
167 // Get the index set for the split at this level of the tree we are traversing to the Schur
168 // complement preconditioner
169 LibmeshPetscCall(PCFieldSplitGetISByIndex(pc, sub_ksp_index, &is));
170
171 // Store this tree level's index set, which we will eventually use to get the sub-matrices
172 // required for our preconditioning process from the system matrices
173 _index_sets[tree_position] = is;
174
175 // PETSc will set up the child while recursively setting up the field split blocks
176 setFieldSplitPostSetUp(next_pc, tree_position + 1);
177
178 // Free the array of sub linear solvers that got allocated in the PCFieldSplitGetSubKSP call
179 LibmeshPetscCall(PetscFree(subksp));
180}
181
182void
184{
185 KSP * subksp; // This will be length two, with the former being the A KSP and the latter being the
186 // Schur complement KSP
187 KSP schur_complement_ksp;
188 PC lsc_pc;
189 PetscInt num_splits;
190 Mat lsc_pc_pmat;
191 IS velocity_is, pressure_is;
192 PetscInt rstart, rend;
193 PetscBool is_lsc;
194 std::vector<Mat> intermediate_Qs;
195 std::vector<Mat> intermediate_Ls;
196
197 // The mass matrix. This will correspond to velocity degrees of freedom for Elman LSC and pressure
198 // degrees of freedom for Olshanskii LSC or when directly using the mass matrix as a
199 // preconditioner for the Schur complement
200 Mat global_Q = nullptr;
202 {
203 auto & sparse_mass_mat = _current_nl_sys->getMatrix(massMatrixTagID());
205 global_Q = cast_ref<const PetscMatrixBase<Number> &>(
206 cast_ref<StaticCondensation &>(sparse_mass_mat).get_condensed_mat())
207 .mat();
208 else
209 global_Q = cast_ref<PetscMatrixBase<Number> &>(sparse_mass_mat).mat();
210 }
211
212 // The Poisson operator matrix corresponding to the velocity degrees of freedom. This is only used
213 // and is required for Olshanskii LSC preconditioning
214 Mat global_L = nullptr;
215 if (_have_L_matrix)
216 global_L =
217 cast_ref<PetscMatrixBase<Number> &>(_current_nl_sys->getMatrix(LMatrixTagID())).mat();
218
219 //
220 // Process down from our system matrices to the sub-matrix containing the velocity-pressure dofs
221 // for which we are going to be doing the Schur complement preconditioning
222 //
223
224 auto process_intermediate_mats = [this](auto & intermediate_mats, auto parent_mat)
225 {
226 mooseAssert(parent_mat, "This should be non-null");
227 intermediate_mats.resize(_index_sets.size());
228 for (const auto i : index_range(_index_sets))
229 {
230 auto intermediate_is = _index_sets[i];
231 Mat intermediate_mat;
232 LibmeshPetscCall(MatCreateSubMatrix(i == 0 ? parent_mat : intermediate_mats[i - 1],
233 intermediate_is,
234 intermediate_is,
235 MAT_INITIAL_MATRIX,
236 &intermediate_mat));
237 intermediate_mats[i] = intermediate_mat;
238 }
239 return _index_sets.empty() ? parent_mat : intermediate_mats.back();
240 };
241
242 Mat our_parent_Q = nullptr;
244 our_parent_Q = process_intermediate_mats(intermediate_Qs, global_Q);
245 Mat our_parent_L = nullptr;
246 if (_have_L_matrix)
247 our_parent_L = process_intermediate_mats(intermediate_Ls, global_L);
248
249 // There are always two splits in a Schur complement split. The zeroth split is the split with the
250 // on-diagonals, e.g. the velocity dofs. Here we retrive the velocity dofs/index set
251 LibmeshPetscCall(PCFieldSplitGetISByIndex(schur_pc, 0, &velocity_is));
252
253 // Get the rows of the parent velocity-pressure matrix that our process owns
254 LibmeshPetscCall(MatGetOwnershipRange(our_parent_Q, &rstart, &rend));
255
256 if (_commute_lsc)
257 {
258 // If we're commuting LSC, e.g. doing Olshanskii, the user must have provided a Poisson operator
259 // matrix
260 mooseAssert(our_parent_L, "This should be non-null");
261
262 if (!_L)
263 // If this is our first time in this routine, then we create the matrix
264 LibmeshPetscCall(
265 MatCreateSubMatrix(our_parent_L, velocity_is, velocity_is, MAT_INITIAL_MATRIX, &_L));
266 else
267 // Else we reuse the matrix
268 LibmeshPetscCall(
269 MatCreateSubMatrix(our_parent_L, velocity_is, velocity_is, MAT_REUSE_MATRIX, &_L));
270 }
271
272 // Get the local index set complement corresponding to the pressure dofs from the velocity dofs
273 LibmeshPetscCall(ISComplement(velocity_is, rstart, rend, &pressure_is));
274
275 auto create_q_scale_submat =
276 [our_parent_Q, this, velocity_is, pressure_is](const auto & mat_initialization)
277 {
279 {
280 // If we are doing Olshanskii or we are using the pressure matrix directly as the
281 // preconditioner (no LSC), then we must have access to a pressure mass matrix
282 mooseAssert(our_parent_Q, "This should be non-null");
283 // Create a sub-matrix corresponding to the pressure index set
284 LibmeshPetscCall(MatCreateSubMatrix(
285 our_parent_Q, pressure_is, pressure_is, mat_initialization, &_Q_scale));
286 }
287 else if (_have_mass_matrix) // If we don't have a mass matrix and the user has requested scaling
288 // then the diagonal of A will be used
289 {
290 // The user passed us a mass matrix tag; we better have been able to obtain a parent Q in that
291 // case
292 mooseAssert(our_parent_Q, "This should be non-null");
293 // We are not commuting LSC, so we are doing Elman, and the user has passed us a mass matrix
294 // tag. In this case we are creating a velocity mass matrix, so we use the velocity index set
295 LibmeshPetscCall(MatCreateSubMatrix(
296 our_parent_Q, velocity_is, velocity_is, mat_initialization, &_Q_scale));
297 }
298 };
299
300 if (!_Q_scale)
301 // We haven't allocated the scaling matrix yet
302 create_q_scale_submat(MAT_INITIAL_MATRIX);
303 else
304 {
306 {
307 // A11 has mucked with the nonzero pattern
308 LibmeshPetscCall(MatDestroy(&_Q_scale));
309 create_q_scale_submat(MAT_INITIAL_MATRIX);
310 }
311 else
312 // We have allocated the scaling matrix, so we can reuse
313 create_q_scale_submat(MAT_REUSE_MATRIX);
314 }
315
316 // We don't need the pressure index set anymore
317 LibmeshPetscCall(ISDestroy(&pressure_is));
318
319 // Nor the intermediate matrices
320 for (auto & mat : intermediate_Qs)
321 LibmeshPetscCall(MatDestroy(&mat));
322 for (auto & mat : intermediate_Ls)
323 LibmeshPetscCall(MatDestroy(&mat));
324
325 // Get the sub KSP for the Schur split that corresponds to the linear solver for the Schur
326 // complement (e.g. rank equivalent to the pressure rank)
327 LibmeshPetscCall(PCFieldSplitGetSubKSP(schur_pc, &num_splits, &subksp));
328 if (num_splits != 2)
329 mooseError("The number of splits should be two");
330 // The Schur complement linear solver is always at the first index (for the pressure dofs;
331 // velocity dof KSP is at index 0)
332 schur_complement_ksp = subksp[1];
333
335 {
336 mooseAssert(_Q_scale, "This should be non-null");
337 Mat S, A11;
338
339 // Get the Schur complement operator S, which in generic KSP speak is used for the operator A
340 LibmeshPetscCall(KSPGetOperators(schur_complement_ksp, &S, nullptr));
342 {
343 LibmeshPetscCall(
344 MatSchurComplementGetSubMatrices(S, nullptr, nullptr, nullptr, nullptr, &A11));
345 LibmeshPetscCall(MatAXPY(_Q_scale, 1, A11, DIFFERENT_NONZERO_PATTERN));
346 }
347 // Set the Schur complement preconditioner to be the pressure mass matrix
348 LibmeshPetscCall(PCFieldSplitSetSchurPre(schur_pc, PC_FIELDSPLIT_SCHUR_PRE_USER, _Q_scale));
349 // Set, in generic KSP speak, the operators A and P respectively. So our pressure mass matrix is
350 // P
351 LibmeshPetscCall(KSPSetOperators(schur_complement_ksp, S, _Q_scale));
352 }
353 else // We are doing LSC preconditioning
354 {
355 // Get the least squares commutator preconditioner for the Schur complement
356 LibmeshPetscCall(KSPGetPC(schur_complement_ksp, &lsc_pc));
357 // Verify that it's indeed an LSC preconditioner
358 LibmeshPetscCall(PetscObjectTypeCompare(PetscObject(lsc_pc), PCLSC, &is_lsc));
359 if (!is_lsc)
360 mooseError("Not an LSC PC. Please check the 'schur_fs_index' parameter");
361
362 // Get the LSC preconditioner
363 LibmeshPetscCall(PCGetOperators(lsc_pc, nullptr, &lsc_pc_pmat));
364
365 if (_commute_lsc)
366 {
367 // We're doing Olshanskii. We must have a user-provided Poisson operator
368 mooseAssert(_L, "This should be non-null");
369 // Attach our L matrix to the PETSc object. PETSc will use this during the preconditioner
370 // application
371 LibmeshPetscCall(PetscObjectCompose((PetscObject)lsc_pc_pmat, "LSC_L", (PetscObject)_L));
372 // Olshanskii preconditioning requires a pressure mass matrix
373 mooseAssert(_have_mass_matrix, "This is to verify we will enter the next conditional");
374 }
376 {
377 mooseAssert(_Q_scale, "This should be non-null");
378 // Attach our scaling/mass matrix to the PETSc object. PETSc will use this during the
379 // preconditioner application
380 LibmeshPetscCall(
381 PetscObjectCompose((PetscObject)lsc_pc_pmat, "LSC_Qscale", (PetscObject)_Q_scale));
382 }
383 }
384
385 // Free the sub-KSP array that was allocated during PCFieldSplitGetSubKSP
386 LibmeshPetscCall(PetscFree(subksp));
387}
388
389void
391{
393
395 {
396 mooseAssert(
398 "If we don't have a mass matrix, which is only supported for traditional LSC "
399 "preconditioning (e.g. Elman, not Olshanskii), then we also shouldn't have an L matrix "
400 "because we automatically form the L matrix when doing traditional LSC preconditioning");
401 return;
402 }
403
404 // Each field split callback will install the callback on the next nested split until PETSc sets
405 // up the target Schur complement split
407 PC pc;
408 LibmeshPetscCall(KSPGetPC(ksp, &pc));
410}
411
412#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
The following methods are specializations for using the Parallel::packed_range_* routines for a vecto...
PetscFunctionReturn(LIBMESH_PETSC_SUCCESS)
auto index_range(const T &sizable)
if(subdm)
PetscErrorCode PetscInt const PetscInt IS * is
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.