LCOV - code coverage report
Current view: top level - src/problems - NavierStokesProblem.C (source / functions) Hit Total Coverage
Test: idaholab/moose navier_stokes: #31405 (292dce) with base fef103 Lines: 123 134 91.8 %
Date: 2025-09-04 07:54:44 Functions: 11 11 100.0 %
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         370 : NavierStokesProblem::validParams()
      22             : {
      23         370 :   InputParameters params = FEProblem::validParams();
      24         370 :   params.addClassDescription("A problem that handles Schur complement preconditioning of the "
      25             :                              "incompressible Navier-Stokes equations");
      26         740 :   params.addParam<TagName>(
      27             :       "mass_matrix", "", "The matrix tag name corresponding to the mass matrix.");
      28         740 :   params.addParam<TagName>(
      29             :       "L_matrix",
      30             :       "",
      31             :       "The matrix tag name corresponding to the diffusive part of the velocity equations.");
      32         740 :   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         740 :   params.addParam<bool>("use_pressure_mass_matrix",
      38         740 :                         false,
      39             :                         "Whether to just use the pressure mass matrix as the preconditioner for "
      40             :                         "the Schur complement");
      41         740 :   params.addParam<bool>(
      42             :       "commute_lsc",
      43         740 :       false,
      44             :       "Whether to use the commuted form of the LSC preconditioner, created by Olshanskii");
      45         370 :   return params;
      46           0 : }
      47             : 
      48         185 : NavierStokesProblem::NavierStokesProblem(const InputParameters & parameters) : FEProblem(parameters)
      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         185 :     _commute_lsc(getParam<bool>("commute_lsc")),
      57         370 :     _mass_matrix(getParam<TagName>("mass_matrix")),
      58         370 :     _L_matrix(getParam<TagName>("L_matrix")),
      59         185 :     _have_mass_matrix(!_mass_matrix.empty()),
      60         185 :     _have_L_matrix(!_L_matrix.empty()),
      61         370 :     _pressure_mass_matrix_as_pre(getParam<bool>("use_pressure_mass_matrix")),
      62         555 :     _schur_fs_index(getParam<std::vector<unsigned int>>("schur_fs_index"))
      63             : {
      64         185 :   if (_commute_lsc)
      65             :   {
      66          19 :     if (!_have_mass_matrix)
      67           0 :       paramError("mass_matrix",
      68             :                  "A pressure mass matrix must be provided if we are commuting the LSC commutator.");
      69          19 :     if (!_have_L_matrix)
      70           0 :       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         166 :   else if (_have_L_matrix)
      75           0 :     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             : 
      81         185 :   if (_pressure_mass_matrix_as_pre && !_have_mass_matrix)
      82           0 :     paramError("mass_matrix",
      83             :                "If 'use_pressure_mass_matrix', then a pressure 'mass_matrix' must be provided");
      84         185 : }
      85             : 
      86         370 : NavierStokesProblem::~NavierStokesProblem()
      87             : {
      88             :   auto ierr = (PetscErrorCode)0;
      89         185 :   if (_Q_scale)
      90             :   {
      91         136 :     ierr = MatDestroy(&_Q_scale);
      92         136 :     CHKERRABORT(this->comm().get(), ierr);
      93             :   }
      94         185 :   if (_L)
      95             :   {
      96          14 :     ierr = MatDestroy(&_L);
      97          14 :     CHKERRABORT(this->comm().get(), ierr);
      98             :   }
      99         370 : }
     100             : 
     101             : void
     102         185 : NavierStokesProblem::initialSetup()
     103             : {
     104         185 :   FEProblem::initialSetup();
     105         370 :   for (const auto & solver_sys : _solver_systems)
     106         185 :     if (solver_sys->system().has_static_condensation())
     107             :     {
     108          20 :       if (_have_L_matrix)
     109           0 :         mooseError("Static condensation and LSC preconditioning not supported together");
     110          20 :       if (_have_mass_matrix)
     111          20 :         cast_ref<StaticCondensation &>(solver_sys->getMatrix(massMatrixTagID()))
     112             :             .uncondensed_dofs_only();
     113             :     }
     114         185 : }
     115             : KSP
     116        3438 : NavierStokesProblem::findSchurKSP(KSP node, const unsigned int tree_position)
     117             : {
     118        3438 :   auto it = _schur_fs_index.begin() + tree_position;
     119        3438 :   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        1432 :   auto sub_ksp_index = *it;
     130             : 
     131             :   // Get the preconditioner associated with the linear solver
     132        1432 :   LibmeshPetscCall(KSPGetPC(node, &fs_pc));
     133             : 
     134             :   // Verify the preconditioner is a field split preconditioner
     135        1432 :   LibmeshPetscCall(PetscObjectTypeCompare((PetscObject)fs_pc, PCFIELDSPLIT, &is_fs));
     136        1432 :   if (!is_fs)
     137           0 :     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        1432 :   LibmeshPetscCall(PCSetUp(fs_pc));
     142             : 
     143             :   // Get the linear solvers associated with each split
     144        1432 :   LibmeshPetscCall(PCFieldSplitGetSubKSP(fs_pc, &num_splits, &subksp));
     145        1432 :   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        1432 :   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        1432 :   _index_sets.push_back(is);
     154             : 
     155             :   // Free the array of sub linear solvers that got allocated in the PCFieldSplitGetSubKSP call
     156        1432 :   LibmeshPetscCall(PetscFree(subksp));
     157             : 
     158             :   // Continue traversing down the tree towards the Schur complement linear solver/preconditioner
     159        1432 :   return findSchurKSP(next_ksp, tree_position + 1);
     160             : }
     161             : 
     162             : void
     163        2006 : NavierStokesProblem::setupLSCMatrices(KSP schur_ksp)
     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        2006 :   LibmeshPetscCall(KSPGetPC(schur_ksp, &schur_pc));
     179        2006 :   LibmeshPetscCall(PetscObjectTypeCompare((PetscObject)schur_pc, PCFIELDSPLIT, &is_fs));
     180        2006 :   if (!is_fs)
     181           0 :     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        2006 :   if (_have_mass_matrix)
     188             :   {
     189        2006 :     auto & sparse_mass_mat = _current_nl_sys->getMatrix(massMatrixTagID());
     190        2006 :     if (_current_nl_sys->system().has_static_condensation())
     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        2006 :   if (_have_L_matrix)
     202             :     global_L =
     203          42 :         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        2048 :   auto process_intermediate_mats = [this](auto & intermediate_mats, auto parent_mat)
     211             :   {
     212             :     mooseAssert(parent_mat, "This should be non-null");
     213        2048 :     intermediate_mats.resize(_index_sets.size());
     214        3480 :     for (const auto i : index_range(_index_sets))
     215             :     {
     216        1432 :       auto intermediate_is = _index_sets[i];
     217             :       Mat intermediate_mat;
     218        1432 :       LibmeshPetscCall(MatCreateSubMatrix(i == 0 ? parent_mat : intermediate_mats[i - 1],
     219             :                                           intermediate_is,
     220             :                                           intermediate_is,
     221             :                                           MAT_INITIAL_MATRIX,
     222             :                                           &intermediate_mat));
     223        1432 :       intermediate_mats[i] = intermediate_mat;
     224             :     }
     225        2048 :     return _index_sets.empty() ? parent_mat : intermediate_mats.back();
     226        2006 :   };
     227             : 
     228             :   Mat our_parent_Q = nullptr;
     229        2006 :   if (_have_mass_matrix)
     230        2006 :     our_parent_Q = process_intermediate_mats(intermediate_Qs, global_Q);
     231             :   Mat our_parent_L = nullptr;
     232        2006 :   if (_have_L_matrix)
     233          42 :     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        2006 :   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        2006 :   LibmeshPetscCall(PCFieldSplitGetISByIndex(schur_pc, 0, &velocity_is));
     242             : 
     243             :   // Get the rows of the parent velocity-pressure matrix that our process owns
     244        2006 :   LibmeshPetscCall(MatGetOwnershipRange(our_parent_Q, &rstart, &rend));
     245             : 
     246        2006 :   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          42 :     if (!_L)
     253             :       // If this is our first time in this routine, then we create the matrix
     254          14 :       LibmeshPetscCall(
     255             :           MatCreateSubMatrix(our_parent_L, velocity_is, velocity_is, MAT_INITIAL_MATRIX, &_L));
     256             :     else
     257             :       // Else we reuse the matrix
     258          28 :       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        2006 :   LibmeshPetscCall(ISComplement(velocity_is, rstart, rend, &pressure_is));
     264             : 
     265             :   auto create_q_scale_submat =
     266        2006 :       [our_parent_Q, this, velocity_is, pressure_is](const auto & mat_initialization)
     267             :   {
     268        2006 :     if (_commute_lsc || _pressure_mass_matrix_as_pre)
     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         392 :       LibmeshPetscCall(MatCreateSubMatrix(
     275             :           our_parent_Q, pressure_is, pressure_is, mat_initialization, &_Q_scale));
     276             :     }
     277        1614 :     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        1614 :       LibmeshPetscCall(MatCreateSubMatrix(
     286             :           our_parent_Q, velocity_is, velocity_is, mat_initialization, &_Q_scale));
     287             :     }
     288        2006 :   };
     289             : 
     290        2006 :   if (!_Q_scale)
     291             :     // We haven't allocated the scaling matrix yet
     292         136 :     create_q_scale_submat(MAT_INITIAL_MATRIX);
     293             :   else
     294             :     // We have allocated the scaling matrix, so we can reuse
     295        1870 :     create_q_scale_submat(MAT_REUSE_MATRIX);
     296             : 
     297             :   // We don't need the pressure index set anymore
     298        2006 :   LibmeshPetscCall(ISDestroy(&pressure_is));
     299             : 
     300             :   // Nor the intermediate matrices
     301        3438 :   for (auto & mat : intermediate_Qs)
     302        1432 :     LibmeshPetscCall(MatDestroy(&mat));
     303        2006 :   for (auto & mat : intermediate_Ls)
     304           0 :     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        2006 :   LibmeshPetscCall(PCFieldSplitGetSubKSP(schur_pc, &num_splits, &subksp));
     309        2006 :   if (num_splits != 2)
     310           0 :     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        2006 :   schur_complement_ksp = subksp[1];
     314             : 
     315        2006 :   if (_pressure_mass_matrix_as_pre)
     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         350 :     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         350 :     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         350 :     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        1656 :     LibmeshPetscCall(KSPGetPC(schur_complement_ksp, &lsc_pc));
     331             :     // Verify that it's indeed an LSC preconditioner
     332        1656 :     LibmeshPetscCall(PetscObjectTypeCompare(PetscObject(lsc_pc), PCLSC, &is_lsc));
     333        1656 :     if (!is_lsc)
     334           0 :       mooseError("Not an LSC PC. Please check the 'schur_fs_index' parameter");
     335             : 
     336             :     // Get the LSC preconditioner
     337        1656 :     LibmeshPetscCall(PCGetOperators(lsc_pc, NULL, &lsc_pc_pmat));
     338             : 
     339        1656 :     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          42 :       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        1656 :     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        1656 :       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        2006 :   LibmeshPetscCall(PetscFree(subksp));
     361        2006 : }
     362             : 
     363             : PetscErrorCode
     364        2006 : navierStokesKSPPreSolve(KSP root_ksp, Vec /*rhs*/, Vec /*x*/, void * context)
     365             : {
     366             :   PetscFunctionBegin;
     367             : 
     368             :   auto * ns_problem = static_cast<NavierStokesProblem *>(context);
     369             :   ns_problem->clearIndexSets();
     370        2006 :   auto schur_ksp = ns_problem->findSchurKSP(root_ksp, 0);
     371        2006 :   ns_problem->setupLSCMatrices(schur_ksp);
     372             : 
     373        2006 :   PetscFunctionReturn(PETSC_SUCCESS);
     374             : }
     375             : 
     376             : void
     377         390 : NavierStokesProblem::initPetscOutputAndSomeSolverSettings()
     378             : {
     379         390 :   FEProblem::initPetscOutputAndSomeSolverSettings();
     380             : 
     381         390 :   if (!_have_mass_matrix)
     382             :   {
     383             :     mooseAssert(
     384             :         !_have_L_matrix,
     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
     392         376 :   auto ksp = currentNonlinearSystem().getFieldSplitPreconditioner().getKSP();
     393         376 :   LibmeshPetscCall(KSPSetPreSolve(ksp, &navierStokesKSPPreSolve, this));
     394             : }
     395             : 
     396             : #endif

Generated by: LCOV version 1.14