LCOV - code coverage report
Current view: top level - src/problems - NavierStokesProblem.C (source / functions) Hit Total Coverage
Test: idaholab/moose navier_stokes: #32971 (54bef8) with base c6cf66 Lines: 130 140 92.9 %
Date: 2026-05-29 20:37:52 Functions: 10 11 90.9 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "FieldSplitPreconditioner.h"
      13             : #include "libmesh/petsc_matrix.h"
      14             : #include "libmesh/static_condensation.h"
      15             : 
      16             : using namespace libMesh;
      17             : 
      18             : registerMooseObject("NavierStokesApp", NavierStokesProblem);
      19             : 
      20             : InputParameters
      21         232 : NavierStokesProblem::validParams()
      22             : {
      23         232 :   InputParameters params = FEProblem::validParams();
      24         232 :   params.addClassDescription("A problem that handles Schur complement preconditioning of the "
      25             :                              "incompressible Navier-Stokes equations");
      26         464 :   params.addParam<TagName>(
      27             :       "mass_matrix", "", "The matrix tag name corresponding to the mass matrix.");
      28         464 :   params.addParam<TagName>(
      29             :       "L_matrix",
      30             :       "",
      31             :       "The matrix tag name corresponding to the diffusive part of the velocity equations.");
      32         464 :   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         464 :   MooseEnum set_schur_pre("false mass a11_and_mass", "false");
      38         464 :   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         464 :   params.addParam<bool>(
      43             :       "commute_lsc",
      44         464 :       false,
      45             :       "Whether to use the commuted form of the LSC preconditioner, created by Olshanskii");
      46         232 :   return params;
      47         232 : }
      48             : 
      49         116 : NavierStokesProblem::NavierStokesProblem(const InputParameters & parameters) : FEProblem(parameters)
      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         116 :     _commute_lsc(getParam<bool>("commute_lsc")),
      58         232 :     _mass_matrix(getParam<TagName>("mass_matrix")),
      59         232 :     _L_matrix(getParam<TagName>("L_matrix")),
      60         116 :     _have_mass_matrix(!_mass_matrix.empty()),
      61         116 :     _have_L_matrix(!_L_matrix.empty()),
      62         232 :     _set_schur_pre((getParam<MooseEnum>("set_schur_pre").getEnum<SetSchurPreType>())),
      63         348 :     _schur_fs_index(getParam<std::vector<unsigned int>>("schur_fs_index"))
      64             : {
      65         116 :   if (_commute_lsc)
      66             :   {
      67           9 :     if (!_have_mass_matrix)
      68           0 :       paramError("mass_matrix",
      69             :                  "A pressure mass matrix must be provided if we are commuting the LSC commutator.");
      70           9 :     if (!_have_L_matrix)
      71           0 :       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         107 :   else if (_have_L_matrix)
      76           0 :     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         116 :   if ((_set_schur_pre != SetSchurPreType::FALSE) && !_have_mass_matrix)
      83           0 :     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         116 : }
      88             : 
      89         116 : NavierStokesProblem::~NavierStokesProblem()
      90             : {
      91             :   auto ierr = (PetscErrorCode)0;
      92         116 :   if (_Q_scale)
      93             :   {
      94          99 :     ierr = MatDestroy(&_Q_scale);
      95          99 :     CHKERRABORT(this->comm().get(), ierr);
      96             :   }
      97         116 :   if (_L)
      98             :   {
      99           8 :     ierr = MatDestroy(&_L);
     100           8 :     CHKERRABORT(this->comm().get(), ierr);
     101             :   }
     102         116 : }
     103             : 
     104             : void
     105         116 : NavierStokesProblem::initialSetup()
     106             : {
     107         116 :   FEProblem::initialSetup();
     108         232 :   for (const auto & solver_sys : _solver_systems)
     109         116 :     if (solver_sys->system().has_static_condensation())
     110             :     {
     111          29 :       if (_have_L_matrix)
     112           0 :         mooseError("Static condensation and LSC preconditioning not supported together");
     113          29 :       if (_have_mass_matrix)
     114          29 :         cast_ref<StaticCondensation &>(solver_sys->getMatrix(massMatrixTagID()))
     115             :             .uncondensed_dofs_only();
     116             :     }
     117         116 : }
     118             : KSP
     119        2509 : NavierStokesProblem::findSchurKSP(KSP node, const unsigned int tree_position)
     120             : {
     121        2509 :   auto it = _schur_fs_index.begin() + tree_position;
     122        2509 :   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        1074 :   auto sub_ksp_index = *it;
     133             : 
     134             :   // Get the preconditioner associated with the linear solver
     135        1074 :   LibmeshPetscCall(KSPGetPC(node, &fs_pc));
     136             : 
     137             :   // Verify the preconditioner is a field split preconditioner
     138        1074 :   LibmeshPetscCall(PetscObjectTypeCompare((PetscObject)fs_pc, PCFIELDSPLIT, &is_fs));
     139        1074 :   if (!is_fs)
     140           0 :     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        1074 :   LibmeshPetscCall(PCSetUp(fs_pc));
     145             : 
     146             :   // Get the linear solvers associated with each split
     147        1074 :   LibmeshPetscCall(PCFieldSplitGetSubKSP(fs_pc, &num_splits, &subksp));
     148        1074 :   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        1074 :   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        1074 :   _index_sets.push_back(is);
     157             : 
     158             :   // Free the array of sub linear solvers that got allocated in the PCFieldSplitGetSubKSP call
     159        1074 :   LibmeshPetscCall(PetscFree(subksp));
     160             : 
     161             :   // Continue traversing down the tree towards the Schur complement linear solver/preconditioner
     162        1074 :   return findSchurKSP(next_ksp, tree_position + 1);
     163             : }
     164             : 
     165             : void
     166        1435 : NavierStokesProblem::setupLSCMatrices(KSP schur_ksp)
     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        1435 :   LibmeshPetscCall(KSPGetPC(schur_ksp, &schur_pc));
     182        1435 :   LibmeshPetscCall(PetscObjectTypeCompare((PetscObject)schur_pc, PCFIELDSPLIT, &is_fs));
     183        1435 :   if (!is_fs)
     184           0 :     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        1435 :   if (_have_mass_matrix)
     191             :   {
     192        1435 :     auto & sparse_mass_mat = _current_nl_sys->getMatrix(massMatrixTagID());
     193        1435 :     if (_current_nl_sys->system().has_static_condensation())
     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        1435 :   if (_have_L_matrix)
     205             :     global_L =
     206          24 :         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        1459 :   auto process_intermediate_mats = [this](auto & intermediate_mats, auto parent_mat)
     214             :   {
     215             :     mooseAssert(parent_mat, "This should be non-null");
     216        1459 :     intermediate_mats.resize(_index_sets.size());
     217        2533 :     for (const auto i : index_range(_index_sets))
     218             :     {
     219        1074 :       auto intermediate_is = _index_sets[i];
     220             :       Mat intermediate_mat;
     221        1074 :       LibmeshPetscCall(MatCreateSubMatrix(i == 0 ? parent_mat : intermediate_mats[i - 1],
     222             :                                           intermediate_is,
     223             :                                           intermediate_is,
     224             :                                           MAT_INITIAL_MATRIX,
     225             :                                           &intermediate_mat));
     226        1074 :       intermediate_mats[i] = intermediate_mat;
     227             :     }
     228        1459 :     return _index_sets.empty() ? parent_mat : intermediate_mats.back();
     229        1435 :   };
     230             : 
     231             :   Mat our_parent_Q = nullptr;
     232        1435 :   if (_have_mass_matrix)
     233        1435 :     our_parent_Q = process_intermediate_mats(intermediate_Qs, global_Q);
     234             :   Mat our_parent_L = nullptr;
     235        1435 :   if (_have_L_matrix)
     236          24 :     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        1435 :   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        1435 :   LibmeshPetscCall(PCFieldSplitGetISByIndex(schur_pc, 0, &velocity_is));
     245             : 
     246             :   // Get the rows of the parent velocity-pressure matrix that our process owns
     247        1435 :   LibmeshPetscCall(MatGetOwnershipRange(our_parent_Q, &rstart, &rend));
     248             : 
     249        1435 :   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          24 :     if (!_L)
     256             :       // If this is our first time in this routine, then we create the matrix
     257           8 :       LibmeshPetscCall(
     258             :           MatCreateSubMatrix(our_parent_L, velocity_is, velocity_is, MAT_INITIAL_MATRIX, &_L));
     259             :     else
     260             :       // Else we reuse the matrix
     261          16 :       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        1435 :   LibmeshPetscCall(ISComplement(velocity_is, rstart, rend, &pressure_is));
     267             : 
     268             :   auto create_q_scale_submat =
     269        1435 :       [our_parent_Q, this, velocity_is, pressure_is](const auto & mat_initialization)
     270             :   {
     271        1435 :     if (_commute_lsc || (_set_schur_pre != SetSchurPreType::FALSE))
     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         257 :       LibmeshPetscCall(MatCreateSubMatrix(
     278             :           our_parent_Q, pressure_is, pressure_is, mat_initialization, &_Q_scale));
     279             :     }
     280        1178 :     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        1178 :       LibmeshPetscCall(MatCreateSubMatrix(
     289             :           our_parent_Q, velocity_is, velocity_is, mat_initialization, &_Q_scale));
     290             :     }
     291        1435 :   };
     292             : 
     293        1435 :   if (!_Q_scale)
     294             :     // We haven't allocated the scaling matrix yet
     295          99 :     create_q_scale_submat(MAT_INITIAL_MATRIX);
     296             :   else
     297             :   {
     298        1336 :     if (_set_schur_pre == SetSchurPreType::A11_AND_MASS)
     299             :     {
     300             :       // A11 has mucked with the nonzero pattern
     301          18 :       LibmeshPetscCall(MatDestroy(&_Q_scale));
     302          18 :       create_q_scale_submat(MAT_INITIAL_MATRIX);
     303             :     }
     304             :     else
     305             :       // We have allocated the scaling matrix, so we can reuse
     306        1318 :       create_q_scale_submat(MAT_REUSE_MATRIX);
     307             :   }
     308             : 
     309             :   // We don't need the pressure index set anymore
     310        1435 :   LibmeshPetscCall(ISDestroy(&pressure_is));
     311             : 
     312             :   // Nor the intermediate matrices
     313        2509 :   for (auto & mat : intermediate_Qs)
     314        1074 :     LibmeshPetscCall(MatDestroy(&mat));
     315        1435 :   for (auto & mat : intermediate_Ls)
     316           0 :     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        1435 :   LibmeshPetscCall(PCFieldSplitGetSubKSP(schur_pc, &num_splits, &subksp));
     321        1435 :   if (num_splits != 2)
     322           0 :     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        1435 :   schur_complement_ksp = subksp[1];
     326             : 
     327        1435 :   if (_set_schur_pre != SetSchurPreType::FALSE)
     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         233 :     LibmeshPetscCall(KSPGetOperators(schur_complement_ksp, &S, nullptr));
     334         233 :     if (_set_schur_pre == SetSchurPreType::A11_AND_MASS)
     335             :     {
     336          32 :       LibmeshPetscCall(
     337             :           MatSchurComplementGetSubMatrices(S, nullptr, nullptr, nullptr, nullptr, &A11));
     338          32 :       LibmeshPetscCall(MatAXPY(_Q_scale, 1, A11, DIFFERENT_NONZERO_PATTERN));
     339             :     }
     340             :     // Set the Schur complement preconditioner to be the pressure mass matrix
     341         233 :     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         233 :     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        1202 :     LibmeshPetscCall(KSPGetPC(schur_complement_ksp, &lsc_pc));
     350             :     // Verify that it's indeed an LSC preconditioner
     351        1202 :     LibmeshPetscCall(PetscObjectTypeCompare(PetscObject(lsc_pc), PCLSC, &is_lsc));
     352        1202 :     if (!is_lsc)
     353           0 :       mooseError("Not an LSC PC. Please check the 'schur_fs_index' parameter");
     354             : 
     355             :     // Get the LSC preconditioner
     356        1202 :     LibmeshPetscCall(PCGetOperators(lsc_pc, nullptr, &lsc_pc_pmat));
     357             : 
     358        1202 :     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          24 :       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        1202 :     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        1202 :       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        1435 :   LibmeshPetscCall(PetscFree(subksp));
     380        1435 : }
     381             : 
     382             : PetscErrorCode
     383        1435 : navierStokesKSPPreSolve(KSP root_ksp, Vec /*rhs*/, Vec /*x*/, void * context)
     384             : {
     385             :   PetscFunctionBegin;
     386             : 
     387             :   auto * ns_problem = static_cast<NavierStokesProblem *>(context);
     388             :   ns_problem->clearIndexSets();
     389        1435 :   auto schur_ksp = ns_problem->findSchurKSP(root_ksp, 0);
     390        1435 :   ns_problem->setupLSCMatrices(schur_ksp);
     391             : 
     392        1435 :   PetscFunctionReturn(PETSC_SUCCESS);
     393             : }
     394             : 
     395             : void
     396         289 : NavierStokesProblem::initPetscOutputAndSomeSolverSettings()
     397             : {
     398         289 :   FEProblem::initPetscOutputAndSomeSolverSettings();
     399             : 
     400         289 :   if (!_have_mass_matrix)
     401             :   {
     402             :     mooseAssert(
     403             :         !_have_L_matrix,
     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
     411         281 :   auto ksp = currentNonlinearSystem().getFieldSplitPreconditioner().getKSP();
     412         281 :   LibmeshPetscCall(KSPSetPreSolve(ksp, &navierStokesKSPPreSolve, this));
     413             : }
     414             : 
     415             : #endif

Generated by: LCOV version 1.14