LCOV - code coverage report
Current view: top level - src/preconditioners - VariableCondensationPreconditioner.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 344 386 89.1 %
Date: 2025-07-17 01:28:37 Functions: 20 20 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 "VariableCondensationPreconditioner.h"
      11             : 
      12             : // MOOSE includes
      13             : #include "FEProblem.h"
      14             : #include "MooseUtils.h"
      15             : #include "MooseVariableFE.h"
      16             : #include "NonlinearSystem.h"
      17             : #include "ComputeJacobianBlocksThread.h"
      18             : #include "MooseEnum.h"
      19             : 
      20             : #include "libmesh/coupling_matrix.h"
      21             : #include "libmesh/libmesh_common.h"
      22             : #include "libmesh/equation_systems.h"
      23             : #include "libmesh/nonlinear_implicit_system.h"
      24             : #include "libmesh/nonlinear_solver.h"
      25             : #include "libmesh/linear_implicit_system.h"
      26             : #include "libmesh/transient_system.h"
      27             : #include "libmesh/numeric_vector.h"
      28             : #include "libmesh/sparse_matrix.h"
      29             : #include "libmesh/string_to_enum.h"
      30             : #include "libmesh/mesh_base.h"
      31             : #include "libmesh/variable.h"
      32             : #include "libmesh/petsc_matrix.h"
      33             : #include "libmesh/parallel_object.h"
      34             : #include "libmesh/boundary_info.h"
      35             : 
      36             : #include <petscmat.h>
      37             : 
      38             : using namespace libMesh;
      39             : 
      40             : registerMooseObjectAliased("MooseApp", VariableCondensationPreconditioner, "VCP");
      41             : 
      42             : InputParameters
      43       14345 : VariableCondensationPreconditioner::validParams()
      44             : {
      45       14345 :   InputParameters params = MoosePreconditioner::validParams();
      46             : 
      47       14345 :   params.addClassDescription(
      48             :       "Variable condensation preconditioner (VCP) condenses out specified variable(s) "
      49             :       "from the Jacobian matrix and produces a system of equations with less unkowns to "
      50             :       "be solved by the underlying preconditioners.");
      51             : 
      52       14345 :   params.addParam<std::vector<NonlinearVariableName>>(
      53             :       "coupled_groups",
      54             :       {},
      55             :       "List multiple space separated groups of comma separated variables. "
      56             :       "Off-diagonal jacobians will be generated for all pairs within a group.");
      57             : 
      58       43035 :   params.addParam<bool>(
      59             :       "is_lm_coupling_diagonal",
      60       28690 :       false,
      61             :       "Set to true if you are sure the coupling matrix between Lagrange multiplier variable and "
      62             :       "the coupled primal variable is strict diagonal. This will speedup the linear solve. "
      63             :       "Otherwise set to false to ensure linear solve accuracy.");
      64       43035 :   params.addParam<bool>(
      65             :       "adaptive_condensation",
      66       28690 :       true,
      67             :       "By default VCP will check the Jacobian and only condense the rows with zero diagonals. Set "
      68             :       "to false if you want to condense out all the specified variable dofs.");
      69       14345 :   params.addRequiredParam<std::vector<std::string>>("preconditioner", "Preconditioner type.");
      70       14345 :   params.addRequiredParam<std::vector<std::string>>(
      71             :       "lm_variable",
      72             :       "Name of the variable(s) that is to be condensed out. Usually "
      73             :       "this will be the Lagrange multiplier variable(s).");
      74       14345 :   params.addRequiredParam<std::vector<std::string>>(
      75             :       "primary_variable",
      76             :       "Name of the variable(s) that couples with the variable(s) specified in the `variable` "
      77             :       "block. Usually this is the primary variable that the Lagrange multiplier correspond to.");
      78       14345 :   return params;
      79           0 : }
      80             : 
      81          40 : VariableCondensationPreconditioner::VariableCondensationPreconditioner(
      82          40 :     const InputParameters & params)
      83             :   : MoosePreconditioner(params),
      84             :     Preconditioner<Number>(MoosePreconditioner::_communicator),
      85          80 :     _nl(_fe_problem.getNonlinearSystemBase(_nl_sys_num)),
      86          40 :     _mesh(_fe_problem.mesh()),
      87          40 :     _dofmap(_nl.system().get_dof_map()),
      88          40 :     _is_lm_coupling_diagonal(getParam<bool>("is_lm_coupling_diagonal")),
      89          40 :     _adaptive_condensation(getParam<bool>("adaptive_condensation")),
      90          40 :     _n_vars(_nl.nVariables()),
      91          40 :     _lm_var_names(getParam<std::vector<std::string>>("lm_variable")),
      92          40 :     _primary_var_names(getParam<std::vector<std::string>>("primary_variable")),
      93          40 :     _D(std::make_unique<PetscMatrix<Number>>(MoosePreconditioner::_communicator)),
      94          40 :     _M(std::make_unique<PetscMatrix<Number>>(MoosePreconditioner::_communicator)),
      95          40 :     _K(std::make_unique<PetscMatrix<Number>>(MoosePreconditioner::_communicator)),
      96          40 :     _dinv(nullptr),
      97          40 :     _J_condensed(std::make_unique<PetscMatrix<Number>>(MoosePreconditioner::_communicator)),
      98          40 :     _x_hat(NumericVector<Number>::build(MoosePreconditioner::_communicator)),
      99          40 :     _y_hat(NumericVector<Number>::build(MoosePreconditioner::_communicator)),
     100          40 :     _primary_rhs_vec(NumericVector<Number>::build(MoosePreconditioner::_communicator)),
     101          40 :     _lm_sol_vec(NumericVector<Number>::build(MoosePreconditioner::_communicator)),
     102          40 :     _need_condense(true),
     103          40 :     _init_timer(registerTimedSection("init", 2)),
     104         200 :     _apply_timer(registerTimedSection("apply", 1))
     105             : {
     106          40 :   if (_lm_var_names.size() != _primary_var_names.size())
     107           0 :     paramError("coupled_variable", "coupled_variable should have the same size as the variable.");
     108             : 
     109          40 :   if (!_mesh.getMesh().is_replicated())
     110           0 :     mooseError("The VariableCondensationPreconditioner cannot be used with DistributedMesh");
     111             : 
     112             :   // get variable ids from the variable names
     113          80 :   for (const auto & var_name : _lm_var_names)
     114             :   {
     115          40 :     if (!_nl.system().has_variable(var_name))
     116           0 :       paramError("variable ", var_name, " does not exist in the system");
     117          40 :     const unsigned int id = _nl.system().variable_number(var_name);
     118          40 :     _lm_var_ids.push_back(id);
     119             :   }
     120             : 
     121             :   // get coupled variable ids from the coupled variable names
     122          80 :   for (const auto & var_name : _primary_var_names)
     123             :   {
     124          40 :     if (!_nl.system().has_variable(var_name))
     125           0 :       paramError("coupled_variable ", var_name, " does not exist in the system");
     126          40 :     const unsigned int id = _nl.system().variable_number(var_name);
     127          40 :     _primary_var_ids.push_back(id);
     128             :   }
     129             : 
     130             :   // PC type
     131          40 :   const std::vector<std::string> & pc_type = getParam<std::vector<std::string>>("preconditioner");
     132          40 :   if (pc_type.size() > 1)
     133           0 :     mooseWarning("We only use one preconditioner type in VCP, the ",
     134           0 :                  pc_type[0],
     135             :                  " preconditioner is utilized.");
     136          40 :   _pre_type = Utility::string_to_enum<PreconditionerType>(pc_type[0]);
     137             : 
     138             :   // The following obtains and sets the coupling matrix.
     139             :   // TODO: This part can be refactored together with what are in other classes, e.g.,
     140             :   // PhysicsBasedPreconditioner
     141          40 :   std::unique_ptr<CouplingMatrix> cm = std::make_unique<CouplingMatrix>(_n_vars);
     142          40 :   const bool full = getParam<bool>("full");
     143             : 
     144          40 :   if (!full)
     145             :   {
     146             :     // put 1s on diagonal
     147           0 :     for (const auto i : make_range(_n_vars))
     148           0 :       (*cm)(i, i) = 1;
     149             : 
     150             :     // off-diagonal entries from the off_diag_row and off_diag_column parameters
     151           0 :     std::vector<std::vector<unsigned int>> off_diag(_n_vars);
     152           0 :     if (isParamValid("off_diag_row") && isParamValid("off_diag_column"))
     153             : 
     154           0 :       for (const auto i : index_range(getParam<std::vector<NonlinearVariableName>>("off_diag_row")))
     155             :       {
     156             :         const unsigned int row =
     157           0 :             _nl.getVariable(0, getParam<std::vector<NonlinearVariableName>>("off_diag_row")[i])
     158           0 :                 .number();
     159             :         const unsigned int column =
     160           0 :             _nl.getVariable(0, getParam<std::vector<NonlinearVariableName>>("off_diag_column")[i])
     161           0 :                 .number();
     162           0 :         (*cm)(row, column) = 1;
     163             :       }
     164             : 
     165             :     // off-diagonal entries from the coupled_groups parameters
     166           0 :     for (const auto & coupled_group :
     167           0 :          getParam<std::vector<NonlinearVariableName>>("coupled_groups"))
     168             :     {
     169           0 :       std::vector<NonlinearVariableName> vars;
     170           0 :       MooseUtils::tokenize<NonlinearVariableName>(coupled_group, vars, 1, ",");
     171           0 :       for (unsigned int j : index_range(vars))
     172           0 :         for (unsigned int k = j + 1; k < vars.size(); ++k)
     173             :         {
     174           0 :           const unsigned int row = _nl.getVariable(0, vars[j]).number();
     175           0 :           const unsigned int column = _nl.getVariable(0, vars[k]).number();
     176           0 :           (*cm)(row, column) = 1;
     177           0 :           (*cm)(column, row) = 1;
     178             :         }
     179           0 :     }
     180           0 :   }
     181             :   else
     182             :   {
     183         120 :     for (unsigned int i = 0; i < _n_vars; i++)
     184         240 :       for (unsigned int j = 0; j < _n_vars; j++)
     185         160 :         (*cm)(i, j) = 1;
     186             :   }
     187             : 
     188          40 :   setCouplingMatrix(std::move(cm));
     189             : 
     190          40 :   _nl.attachPreconditioner(this);
     191          40 : }
     192             : 
     193          80 : VariableCondensationPreconditioner::~VariableCondensationPreconditioner() { this->clear(); }
     194             : 
     195             : void
     196          64 : VariableCondensationPreconditioner::getDofToCondense()
     197             : {
     198             :   // clean the containers if we want to update the dofs
     199          64 :   _global_lm_dofs.clear();
     200          64 :   _lm_dofs.clear();
     201          64 :   _global_primary_dofs.clear();
     202          64 :   _primary_dofs.clear();
     203          64 :   _map_global_lm_primary.clear();
     204          64 :   _map_global_primary_order.clear();
     205             : 
     206             :   // TODO: this might not work for distributed mesh and needs to be improved
     207          64 :   NodeRange * active_nodes = _mesh.getActiveNodeRange();
     208             : 
     209             :   // loop through the variable ids
     210          64 :   std::vector<dof_id_type> di, cp_di;
     211         128 :   for (const auto & vn : index_range(_lm_var_ids))
     212        1692 :     for (const auto & node : *active_nodes)
     213             :     {
     214        1628 :       di.clear();
     215        1628 :       cp_di.clear();
     216        1628 :       const auto var_id = _lm_var_ids[vn];
     217             :       // get coupled variable id
     218        1628 :       const auto cp_var_id = _primary_var_ids[vn];
     219             :       // get var and cp_var dofs associated with this node
     220        1628 :       _dofmap.dof_indices(node, di, var_id);
     221             :       // skip when di is empty
     222        1628 :       if (di.empty())
     223        1318 :         continue;
     224         310 :       _dofmap.dof_indices(node, cp_di, cp_var_id);
     225         310 :       if (cp_di.size() != di.size())
     226           0 :         mooseError("variable and coupled variable do not have the same number of dof on node ",
     227           0 :                    node->id(),
     228             :                    ".");
     229         620 :       for (const auto & i : index_range(di))
     230             :       {
     231             :         // when we have adaptive condensation, skip when di does not contain any indices in
     232             :         // _zero_rows
     233         310 :         if (std::find(_zero_rows.begin(), _zero_rows.end(), di[i]) == _zero_rows.end() &&
     234           0 :             _adaptive_condensation)
     235           0 :           break;
     236         310 :         _global_lm_dofs.push_back(di[i]);
     237         310 :         if (_dofmap.local_index(di[i]))
     238         234 :           _lm_dofs.push_back(di[i]);
     239             : 
     240             :         // save the corresponding coupled dof indices
     241         310 :         _global_primary_dofs.push_back(cp_di[i]);
     242         310 :         if (_dofmap.local_index(cp_di[i]))
     243         234 :           _primary_dofs.push_back(cp_di[i]);
     244         310 :         _map_global_lm_primary.insert(std::make_pair(di[i], cp_di[i]));
     245             :       }
     246             :     }
     247             : 
     248             :   // check if we endup with none dof to condense
     249          64 :   if (_global_lm_dofs.empty())
     250             :   {
     251           0 :     _need_condense = false;
     252           0 :     _console << std::endl
     253             :              << "The variable(s) provided do not have a saddle-point character at this step. VCP "
     254           0 :                 "will continue without condensing the dofs."
     255           0 :              << std::endl
     256           0 :              << std::endl;
     257             :   }
     258             :   else
     259          64 :     _need_condense = true;
     260             : 
     261          64 :   std::sort(_global_lm_dofs.begin(), _global_lm_dofs.end());
     262          64 :   std::sort(_lm_dofs.begin(), _lm_dofs.end());
     263             : 
     264          64 :   std::sort(_global_primary_dofs.begin(), _global_primary_dofs.end());
     265          64 :   std::sort(_primary_dofs.begin(), _primary_dofs.end());
     266             : 
     267         374 :   for (const auto & i : index_range(_global_lm_dofs))
     268             :   {
     269         310 :     auto it = _map_global_lm_primary.find(_global_lm_dofs[i]);
     270             :     mooseAssert(it != _map_global_lm_primary.end(), "Index does not exist in the map.");
     271         310 :     _map_global_primary_order.insert(std::make_pair(it->second, i));
     272             :   }
     273          64 : }
     274             : 
     275             : void
     276          64 : VariableCondensationPreconditioner::getDofColRow()
     277             : {
     278             :   // clean the containers if we want to update the dofs
     279          64 :   _global_rows.clear();
     280          64 :   _rows.clear();
     281          64 :   _global_cols.clear();
     282          64 :   _cols.clear();
     283          64 :   _global_rows_to_idx.clear();
     284          64 :   _rows_to_idx.clear();
     285          64 :   _global_cols_to_idx.clear();
     286          64 :   _cols_to_idx.clear();
     287             :   // row: all without primary variable dofs
     288             :   // col: all without lm variable dofs
     289        2002 :   for (dof_id_type i = 0; i < _dofmap.n_dofs(); ++i)
     290             :   {
     291        1938 :     if (std::find(_global_primary_dofs.begin(), _global_primary_dofs.end(), i) !=
     292        3876 :         _global_primary_dofs.end())
     293         310 :       continue;
     294             : 
     295        1628 :     _global_rows.push_back(i);
     296        1628 :     _global_rows_to_idx.insert(std::make_pair(i, _global_rows.size() - 1));
     297        1628 :     if (_dofmap.local_index(i))
     298             :     {
     299        1220 :       _rows.push_back(i);
     300        1220 :       _rows_to_idx.insert(std::make_pair(i, _global_rows_to_idx[i]));
     301             :     }
     302             : 
     303             :     // ensure the lm and primary correspondance, so that the condensed Jacobian has non-zero
     304             :     // diagonal if the dof corresponds to the lm variable, then find the corresponding primary
     305             :     // variable dof and add to _global_cols
     306        1628 :     if (_map_global_lm_primary.find(i) != _map_global_lm_primary.end())
     307             :     {
     308         310 :       auto primary_idx = _map_global_lm_primary[i];
     309         310 :       _global_cols.push_back(primary_idx);
     310         310 :       _global_cols_to_idx.insert(std::make_pair(primary_idx, _global_cols.size() - 1));
     311             : 
     312         310 :       if (_dofmap.local_index(primary_idx))
     313             :       {
     314         234 :         _cols.push_back(primary_idx);
     315         234 :         _cols_to_idx.insert(std::make_pair(primary_idx, _global_cols_to_idx[primary_idx]));
     316             :       }
     317             :     }
     318             :     else // if the dof does not correspond to the lm nor primary variable, just add to _global_cols
     319             :     {
     320        1318 :       _global_cols.push_back(i);
     321        1318 :       _global_cols_to_idx.insert(std::make_pair(i, _global_cols.size() - 1));
     322             : 
     323        1318 :       if (_dofmap.local_index(i))
     324             :       {
     325         986 :         _cols.push_back(i);
     326         986 :         _cols_to_idx.insert(std::make_pair(i, _global_cols_to_idx[i]));
     327             :       }
     328             :     }
     329             :   }
     330          64 : }
     331             : 
     332             : void
     333          72 : VariableCondensationPreconditioner::init()
     334             : {
     335          72 :   TIME_SECTION(_init_timer);
     336             : 
     337          72 :   if (!_preconditioner)
     338             :     _preconditioner =
     339          36 :         Preconditioner<Number>::build_preconditioner(MoosePreconditioner::_communicator);
     340             : 
     341          72 :   _is_initialized = true;
     342          72 : }
     343             : 
     344             : void
     345          64 : VariableCondensationPreconditioner::condenseSystem()
     346             : {
     347             :   // extract _M from the original matrix
     348          64 :   _matrix->create_submatrix(*_M, _rows, _lm_dofs);
     349             : 
     350             :   // get the row associated with the coupled primary variable
     351          64 :   _K->init(_global_primary_dofs.size(), _global_cols.size(), _primary_dofs.size(), _cols.size());
     352             :   // Note: enabling nonzero allocation may be expensive. Improved memeory pre-allocation will be
     353             :   // investigated in the future
     354          64 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     355             :                     MatSetOption(_K->mat(), MAT_NEW_NONZERO_ALLOCATION_ERR, PETSC_FALSE));
     356             :   // here the _global_cols may not be sorted
     357          64 :   _matrix->create_submatrix_nosort(*_K, _global_primary_dofs, _global_cols);
     358             : 
     359          64 :   _matrix->create_submatrix(*_D, _primary_dofs, _lm_dofs);
     360             : 
     361             :   // clean dinv
     362          64 :   if (_dinv)
     363             :   {
     364          28 :     LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), MatDestroy(&_dinv));
     365          28 :     _dinv = nullptr;
     366             :   }
     367             : 
     368             :   // Compute inverse of D
     369          64 :   if (_is_lm_coupling_diagonal)
     370             :     // when _D is strictly diagonal, we only need to compute the reciprocal number of the diagonal
     371             :     // entries
     372          50 :     computeDInverseDiag(_dinv);
     373             :   else
     374             :     // for general cases when _D is not necessarily strict diagonal, we compute the inverse of _D
     375             :     // using LU
     376          14 :     computeDInverse(_dinv);
     377             : 
     378             :   Mat MdinvK;
     379             :   // calculate MdinvK
     380          64 :   LibmeshPetscCallA(
     381             :       this->MoosePreconditioner::comm().get(),
     382             :       MatMatMatMult(_M->mat(), _dinv, _K->mat(), MAT_INITIAL_MATRIX, PETSC_DEFAULT, &MdinvK));
     383          64 :   PetscMatrix<Number> MDinv_K(MdinvK, MoosePreconditioner::_communicator);
     384             : 
     385             :   // Preallocate memory for _J_condensed
     386             :   // memory info is obtained from _matrix and MDinv_K
     387             :   // indices are from _global_rows, _global_cols
     388          64 :   auto pc_original_mat = cast_ptr<PetscMatrix<Number> *>(_matrix);
     389          64 :   preallocateCondensedJacobian(
     390          64 :       *_J_condensed, *pc_original_mat, _rows, _cols, _global_rows, _global_cols, MDinv_K);
     391             : 
     392             :   // Extract unchanged parts from _matrix and add changed parts (MDinv_K) to _J_condensed
     393          64 :   computeCondensedJacobian(*_J_condensed, *pc_original_mat, _global_rows, MDinv_K);
     394             : 
     395             :   // Destroy MdinvK here otherwise we will have memory leak
     396          64 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), MatDestroy(&MdinvK));
     397          64 : }
     398             : 
     399             : void
     400          64 : VariableCondensationPreconditioner::computeCondensedJacobian(PetscMatrix<Number> & condensed_mat,
     401             :                                                              PetscMatrix<Number> & original_mat,
     402             :                                                              const std::vector<dof_id_type> & grows,
     403             :                                                              PetscMatrix<Number> & block_mat)
     404             : {
     405             :   // obtain entries from the original matrix
     406          64 :   PetscInt pc_ncols = 0, block_ncols = 0;
     407             :   const PetscInt *pc_cols, *block_cols;
     408             :   const PetscScalar *pc_vals, *block_vals;
     409             : 
     410             :   // containers for the data
     411          64 :   std::vector<PetscInt> sub_cols;
     412          64 :   std::vector<PetscScalar> sub_vals;
     413             : 
     414        1692 :   for (const auto & i : index_range(grows))
     415             :   {
     416        1628 :     PetscInt sub_rid[] = {static_cast<PetscInt>(i)};
     417        1628 :     PetscInt rid = grows[i];
     418        1628 :     if (grows[i] >= original_mat.row_start() && grows[i] < original_mat.row_stop())
     419             :     {
     420             :       // get one row of data from the original matrix
     421        1220 :       LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     422             :                         MatGetRow(original_mat.mat(), rid, &pc_ncols, &pc_cols, &pc_vals));
     423             :       // get corresponding row of data from the block matrix
     424        1220 :       LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     425             :                         MatGetRow(block_mat.mat(), i, &block_ncols, &block_cols, &block_vals));
     426             :       // extract data from certain cols, subtract the value from the block mat, and save the indices
     427             :       // and entries sub_cols and sub_vals
     428             :       // First, save the submatrix col index and value as a map
     429        1220 :       std::map<PetscInt, PetscScalar> pc_col_map;
     430       15886 :       for (PetscInt pc_idx = 0; pc_idx < pc_ncols; pc_idx++)
     431             :       {
     432             :         // save only if the col exists in the condensed matrix
     433       14666 :         if (_global_cols_to_idx.find(pc_cols[pc_idx]) != _global_cols_to_idx.end())
     434       11338 :           pc_col_map.insert(std::make_pair(_global_cols_to_idx[pc_cols[pc_idx]], pc_vals[pc_idx]));
     435             :       }
     436             :       // Second, check the block cols and calculate new entries for the condensed system
     437       14328 :       for (PetscInt block_idx = 0; block_idx < block_ncols; block_idx++)
     438             :       {
     439       13108 :         PetscInt block_col = block_cols[block_idx];
     440       13108 :         PetscScalar block_val = block_vals[block_idx];
     441             :         // if the block mat has nonzero at the same column, subtract value
     442             :         // otherwise, create a new key and save the negative value from the block matrix
     443       13108 :         if (pc_col_map.find(block_col) != pc_col_map.end())
     444        4510 :           pc_col_map[block_col] -= block_val;
     445             :         else
     446        8598 :           pc_col_map[block_col] = -block_val;
     447             :       }
     448             : 
     449             :       // Third, save keys in the sub_cols and values in the sub_vals
     450        1220 :       for (std::map<PetscInt, PetscScalar>::iterator it = pc_col_map.begin();
     451       21156 :            it != pc_col_map.end();
     452       19936 :            ++it)
     453             :       {
     454       19936 :         sub_cols.push_back(it->first);
     455       19936 :         sub_vals.push_back(it->second);
     456             :       }
     457             : 
     458             :       // Then, set values
     459        1220 :       LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     460             :                         MatSetValues(condensed_mat.mat(),
     461             :                                      1,
     462             :                                      sub_rid,
     463             :                                      sub_vals.size(),
     464             :                                      sub_cols.data(),
     465             :                                      sub_vals.data(),
     466             :                                      INSERT_VALUES));
     467        1220 :       LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     468             :                         MatRestoreRow(original_mat.mat(), rid, &pc_ncols, &pc_cols, &pc_vals));
     469        1220 :       LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     470             :                         MatRestoreRow(block_mat.mat(), i, &block_ncols, &block_cols, &block_vals));
     471             :       // clear data for this row
     472        1220 :       sub_cols.clear();
     473        1220 :       sub_vals.clear();
     474        1220 :     }
     475             :   }
     476          64 :   condensed_mat.close();
     477          64 : }
     478             : 
     479             : void
     480          64 : VariableCondensationPreconditioner::preallocateCondensedJacobian(
     481             :     PetscMatrix<Number> & condensed_mat,
     482             :     PetscMatrix<Number> & original_mat,
     483             :     const std::vector<dof_id_type> & rows,
     484             :     const std::vector<dof_id_type> & cols,
     485             :     const std::vector<dof_id_type> & grows,
     486             :     const std::vector<dof_id_type> & gcols,
     487             :     PetscMatrix<Number> & block_mat)
     488             : {
     489             :   // quantities from the original matrix and the block matrix
     490          64 :   PetscInt ncols = 0, block_ncols = 0;
     491             :   const PetscInt * col_vals;
     492             :   const PetscInt * block_col_vals;
     493             :   const PetscScalar * vals;
     494             :   const PetscScalar * block_vals;
     495             : 
     496          64 :   std::vector<PetscInt> block_cols_to_org; // stores the nonzero column indices of the block
     497             :                                            // matrix w.r.t original matrix
     498             :   std::vector<PetscInt>
     499          64 :       merged_cols; // stores the nonzero column indices estimate of the condensed matrix
     500             : 
     501             :   // number of nonzeros in each row of the DIAGONAL and OFF-DIAGONAL portion of the local
     502             :   // condensed matrix
     503          64 :   std::vector<dof_id_type> n_nz, n_oz;
     504             : 
     505             :   // Get number of nonzeros from original_mat and block_mat for each row
     506        1284 :   for (const auto & row_id : _rows)
     507             :   {
     508             :     // get number of non-zeros in the original matrix
     509        1220 :     LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     510             :                       MatGetRow(original_mat.mat(), row_id, &ncols, &col_vals, &vals));
     511             : 
     512             :     // get number of non-zeros in the block matrix
     513             :     dof_id_type block_row_id; // row id in the block matrix
     514             : 
     515        1220 :     if (_global_rows_to_idx.find(row_id) != _global_rows_to_idx.end())
     516        1220 :       block_row_id = _global_rows_to_idx[row_id];
     517             :     else
     518           0 :       mooseError("DoF ", row_id, " does not exist in the rows of condensed_mat");
     519             : 
     520        1220 :     LibmeshPetscCallA(
     521             :         this->MoosePreconditioner::comm().get(),
     522             :         MatGetRow(block_mat.mat(), block_row_id, &block_ncols, &block_col_vals, &block_vals));
     523             : 
     524             :     // make sure the block index is transformed in terms of the original mat
     525        1220 :     block_cols_to_org.clear();
     526       14328 :     for (PetscInt i = 0; i < block_ncols; ++i)
     527             :     {
     528       13108 :       auto idx = gcols[block_col_vals[i]];
     529       13108 :       block_cols_to_org.push_back(idx);
     530             :     }
     531             : 
     532             :     // Now store nonzero column indices for the condensed Jacobian
     533             :     // merge `col_vals` and `block_cols_to_org` and save the common indices in `merged_cols`.
     534        1220 :     mergeArrays(col_vals, block_cols_to_org.data(), ncols, block_ncols, merged_cols);
     535             : 
     536        1220 :     LibmeshPetscCallA(
     537             :         this->MoosePreconditioner::comm().get(),
     538             :         MatRestoreRow(block_mat.mat(), block_row_id, &block_ncols, &block_col_vals, &block_vals));
     539             : 
     540        1220 :     LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     541             :                       MatRestoreRow(original_mat.mat(), row_id, &ncols, &col_vals, &vals));
     542             : 
     543             :     // Count the nnz for DIAGONAL and OFF-DIAGONAL parts
     544        1220 :     PetscInt row_n_nz = 0, row_n_oz = 0;
     545       24484 :     for (const auto & merged_col : merged_cols)
     546             :     {
     547             :       // find corresponding index in the block mat and skip the cols that do not exist in the
     548             :       // condensed system
     549       23264 :       if (_global_cols_to_idx.find(merged_col) == _global_cols_to_idx.end())
     550        3328 :         continue;
     551             : 
     552       19936 :       dof_id_type col_idx = _global_cols_to_idx[merged_col];
     553             :       // find the corresponding row index
     554       19936 :       dof_id_type row_idx = grows[col_idx];
     555             :       // check whether the index is local;
     556             :       // yes - DIAGONAL, no - OFF-DIAGONAL
     557       19936 :       if (_rows_to_idx.find(row_idx) != _rows_to_idx.end())
     558       17312 :         row_n_nz++;
     559             :       else
     560        2624 :         row_n_oz++;
     561             :     }
     562             : 
     563        1220 :     n_nz.push_back(cast_int<dof_id_type>(row_n_nz));
     564        1220 :     n_oz.push_back(cast_int<dof_id_type>(row_n_oz));
     565             :   }
     566             :   // Then initialize and allocate memory for the condensed system matrix
     567          64 :   condensed_mat.init(grows.size(), gcols.size(), rows.size(), cols.size(), n_nz, n_oz);
     568          64 : }
     569             : 
     570             : void
     571        1220 : VariableCondensationPreconditioner::mergeArrays(const PetscInt * a,
     572             :                                                 const PetscInt * b,
     573             :                                                 const PetscInt & na,
     574             :                                                 const PetscInt & nb,
     575             :                                                 std::vector<PetscInt> & c)
     576             : {
     577        1220 :   c.clear();
     578             : 
     579             :   // use map to store unique elements.
     580        1220 :   std::map<PetscInt, bool> mp;
     581             : 
     582             :   // Inserting values to a map.
     583       15886 :   for (const auto & i : make_range(na))
     584       14666 :     mp[a[i]] = true;
     585             : 
     586       14328 :   for (const auto & i : make_range(nb))
     587       13108 :     mp[b[i]] = true;
     588             : 
     589             :   // Save the merged values to c, if only the value also exist in gcols
     590       24484 :   for (const auto & i : mp)
     591       23264 :     c.push_back(i.first);
     592        1220 : }
     593             : 
     594             : void
     595          64 : VariableCondensationPreconditioner::setup()
     596             : {
     597          64 :   if (_adaptive_condensation)
     598          64 :     findZeroDiagonals(*_matrix, _zero_rows);
     599             : 
     600             :   // save dofs that are to be condensed out
     601          64 :   getDofToCondense();
     602             : 
     603             :   // solve the condensed system only when needed, otherwise solve the original system
     604          64 :   if (_need_condense)
     605             :   {
     606             :     // get condensed dofs for rows and cols
     607          64 :     getDofColRow();
     608             : 
     609          64 :     condenseSystem();
     610             : 
     611             :     // make sure diagonal entries are not empty
     612        1284 :     for (const auto & i : make_range(_J_condensed->row_start(), _J_condensed->row_stop()))
     613        1220 :       _J_condensed->add(i, i, 0.0);
     614          64 :     _J_condensed->close();
     615             : 
     616          64 :     _preconditioner->set_matrix(*_J_condensed);
     617             :   }
     618             :   else
     619           0 :     _preconditioner->set_matrix(*_matrix);
     620             : 
     621          64 :   _preconditioner->set_type(_pre_type);
     622          64 :   _preconditioner->init();
     623          64 : }
     624             : 
     625             : void
     626         378 : VariableCondensationPreconditioner::apply(const NumericVector<Number> & y,
     627             :                                           NumericVector<Number> & x)
     628             : {
     629         378 :   TIME_SECTION(_apply_timer);
     630             : 
     631         378 :   if (_need_condense)
     632             :   {
     633         378 :     getCondensedXY(y, x);
     634             : 
     635         378 :     _preconditioner->apply(*_y_hat, *_x_hat);
     636             : 
     637         378 :     computeCondensedVariables();
     638             : 
     639         378 :     getFullSolution(y, x);
     640             :   }
     641             :   else
     642             :   {
     643           0 :     _preconditioner->apply(y, x);
     644             :   }
     645         378 : }
     646             : 
     647             : void
     648         378 : VariableCondensationPreconditioner::getCondensedXY(const NumericVector<Number> & y,
     649             :                                                    NumericVector<Number> & x)
     650             : {
     651             :   Mat mdinv;
     652             :   // calculate mdinv
     653         378 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     654             :                     MatMatMult(_M->mat(), _dinv, MAT_INITIAL_MATRIX, PETSC_DEFAULT, &mdinv));
     655         378 :   PetscMatrix<Number> MDinv(mdinv, MoosePreconditioner::_communicator);
     656             : 
     657         378 :   _x_hat->init(_J_condensed->n(), _J_condensed->local_n(), false, PARALLEL);
     658         378 :   _y_hat->init(_J_condensed->m(), _J_condensed->local_m(), false, PARALLEL);
     659             : 
     660         378 :   x.create_subvector(*_x_hat, _global_cols);
     661         378 :   y.create_subvector(*_y_hat, _global_rows);
     662             : 
     663         378 :   _primary_rhs_vec->init(MDinv.n(), MDinv.local_n(), false, PARALLEL);
     664             : 
     665             :   std::unique_ptr<NumericVector<Number>> mdinv_primary_rhs(
     666         378 :       NumericVector<Number>::build(MoosePreconditioner::_communicator));
     667         378 :   mdinv_primary_rhs->init(MDinv.m(), MDinv.local_m(), false, PARALLEL);
     668             : 
     669             :   // get _primary_rhs_vec from the original y
     670         378 :   y.create_subvector(*_primary_rhs_vec, _global_primary_dofs);
     671             : 
     672         378 :   MDinv.vector_mult(*mdinv_primary_rhs, *_primary_rhs_vec);
     673         378 :   mdinv_primary_rhs->close();
     674             : 
     675         378 :   (*_y_hat) -= (*mdinv_primary_rhs);
     676             : 
     677         378 :   _y_hat->close();
     678         378 :   _x_hat->close();
     679             : 
     680         378 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), MatDestroy(&mdinv));
     681         378 : }
     682             : 
     683             : void
     684         378 : VariableCondensationPreconditioner::computeCondensedVariables()
     685             : {
     686         378 :   _lm_sol_vec->clear();
     687             : 
     688         378 :   PetscMatrix<Number> Dinv(_dinv, MoosePreconditioner::_communicator);
     689             : 
     690         378 :   _lm_sol_vec->init(_D->m(), _D->local_m(), false, PARALLEL);
     691             : 
     692             :   std::unique_ptr<NumericVector<Number>> K_xhat(
     693         378 :       NumericVector<Number>::build(MoosePreconditioner::_communicator));
     694         378 :   K_xhat->init(_K->m(), _K->local_m(), false, PARALLEL);
     695         378 :   _K->vector_mult(*K_xhat, *_x_hat);
     696         378 :   K_xhat->close();
     697             : 
     698         378 :   (*_primary_rhs_vec) -= (*K_xhat);
     699         378 :   _primary_rhs_vec->close();
     700         378 :   Dinv.vector_mult(*_lm_sol_vec, *_primary_rhs_vec);
     701         378 :   _lm_sol_vec->close();
     702         378 : }
     703             : 
     704             : void
     705         378 : VariableCondensationPreconditioner::getFullSolution(const NumericVector<Number> & /*y*/,
     706             :                                                     NumericVector<Number> & x)
     707             : {
     708         378 :   std::vector<dof_id_type> dof_indices;
     709         378 :   std::vector<Number> vals;
     710             : 
     711             :   // save values and indices from _x_hat and _lm_sol_vec
     712        8222 :   for (const auto & i : make_range(_x_hat->first_local_index(), _x_hat->last_local_index()))
     713             :   {
     714        7844 :     dof_indices.push_back(_global_cols[i]);
     715        7844 :     vals.push_back((*_x_hat)(i));
     716             :   }
     717             : 
     718         378 :   for (const auto & i :
     719        2224 :        make_range(_lm_sol_vec->first_local_index(), _lm_sol_vec->last_local_index()))
     720             :   {
     721        1468 :     dof_indices.push_back(_global_lm_dofs[i]);
     722        1468 :     vals.push_back((*_lm_sol_vec)(i));
     723             :   }
     724             : 
     725         378 :   x.insert(vals.data(), dof_indices);
     726         378 :   x.close();
     727         378 : }
     728             : 
     729             : void
     730          64 : VariableCondensationPreconditioner::findZeroDiagonals(SparseMatrix<Number> & mat,
     731             :                                                       std::vector<dof_id_type> & indices)
     732             : {
     733          64 :   indices.clear();
     734             :   IS zerodiags, zerodiags_all;
     735             :   const PetscInt * petsc_idx;
     736             :   PetscInt nrows;
     737             :   // make sure we have a PETSc matrix
     738          64 :   auto * const petsc_mat = cast_ptr<PetscMatrix<Number> *>(&mat);
     739          64 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     740             :                     MatFindZeroDiagonals(petsc_mat->mat(), &zerodiags));
     741             :   // synchronize all indices
     742          64 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     743             :                     ISAllGather(zerodiags, &zerodiags_all));
     744          64 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     745             :                     ISGetIndices(zerodiags_all, &petsc_idx));
     746          64 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), ISGetSize(zerodiags_all, &nrows));
     747             : 
     748         374 :   for (PetscInt i = 0; i < nrows; ++i)
     749         310 :     indices.push_back(petsc_idx[i]);
     750             : 
     751          64 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     752             :                     ISRestoreIndices(zerodiags_all, &petsc_idx));
     753          64 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), ISDestroy(&zerodiags));
     754          64 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), ISDestroy(&zerodiags_all));
     755          64 : }
     756             : 
     757             : void
     758          40 : VariableCondensationPreconditioner::clear()
     759             : {
     760          40 :   if (_dinv != nullptr)
     761          36 :     LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), MatDestroy(&_dinv));
     762          40 : }
     763             : 
     764             : void
     765          14 : VariableCondensationPreconditioner::computeDInverse(Mat & dinv)
     766             : {
     767             :   Mat F, I, dinv_dense;
     768             :   IS perm, iperm;
     769             :   MatFactorInfo info;
     770             : 
     771          14 :   LibmeshPetscCallA(
     772             :       this->MoosePreconditioner::comm().get(),
     773             :       MatCreateDense(
     774             :           PETSC_COMM_WORLD, _D->local_n(), _D->local_m(), _D->n(), _D->m(), NULL, &dinv_dense));
     775             : 
     776             :   // Create an identity matrix as the right-hand-side
     777          14 :   LibmeshPetscCallA(
     778             :       this->MoosePreconditioner::comm().get(),
     779             :       MatCreateDense(PETSC_COMM_WORLD, _D->local_m(), _D->local_m(), _D->m(), _D->m(), NULL, &I));
     780             : 
     781          70 :   for (unsigned int i = 0; i < _D->m(); ++i)
     782          56 :     LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     783             :                       MatSetValue(I, i, i, 1.0, INSERT_VALUES));
     784             : 
     785          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     786             :                     MatAssemblyBegin(I, MAT_FINAL_ASSEMBLY));
     787          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), MatAssemblyEnd(I, MAT_FINAL_ASSEMBLY));
     788             : 
     789             :   // Factorize D
     790          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     791             :                     MatGetOrdering(_D->mat(), MATORDERINGND, &perm, &iperm));
     792             : 
     793          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), MatFactorInfoInitialize(&info));
     794             : 
     795          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     796             :                     MatGetFactor(_D->mat(), MATSOLVERSUPERLU_DIST, MAT_FACTOR_LU, &F));
     797             : 
     798          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     799             :                     MatLUFactorSymbolic(F, _D->mat(), perm, iperm, &info));
     800             : 
     801          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     802             :                     MatLUFactorNumeric(F, _D->mat(), &info));
     803             : 
     804             :   // Solve for Dinv
     805          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), MatMatSolve(F, I, dinv_dense));
     806             : 
     807          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     808             :                     MatAssemblyBegin(dinv_dense, MAT_FINAL_ASSEMBLY));
     809          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     810             :                     MatAssemblyEnd(dinv_dense, MAT_FINAL_ASSEMBLY));
     811             : 
     812             :   // copy value to dinv
     813          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     814             :                     MatConvert(dinv_dense, MATAIJ, MAT_INITIAL_MATRIX, &dinv));
     815             : 
     816          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), MatDestroy(&dinv_dense));
     817             : 
     818          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), MatDestroy(&I));
     819          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), MatDestroy(&F));
     820          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), ISDestroy(&perm));
     821          14 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(), ISDestroy(&iperm));
     822          14 : }
     823             : 
     824             : void
     825          50 : VariableCondensationPreconditioner::computeDInverseDiag(Mat & dinv)
     826             : {
     827          50 :   auto diag_D = NumericVector<Number>::build(MoosePreconditioner::_communicator);
     828             :   // Initialize dinv
     829          50 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     830             :                     MatCreateAIJ(PETSC_COMM_WORLD,
     831             :                                  _D->local_n(),
     832             :                                  _D->local_m(),
     833             :                                  _D->n(),
     834             :                                  _D->m(),
     835             :                                  1,
     836             :                                  NULL,
     837             :                                  0,
     838             :                                  NULL,
     839             :                                  &dinv));
     840             :   // Allocate storage
     841          50 :   diag_D->init(_D->m(), _D->local_m(), false, PARALLEL);
     842             :   // Fill entries
     843         244 :   for (const auto & i : make_range(_D->row_start(), _D->row_stop()))
     844             :   {
     845         194 :     auto it = _map_global_primary_order.find(_global_primary_dofs[i]);
     846             :     mooseAssert(it != _map_global_primary_order.end(), "Index does not exist in the map.");
     847         194 :     diag_D->set(it->second, (*_D)(i, it->second));
     848             :   }
     849             : 
     850         244 :   for (const auto & i : make_range(_D->row_start(), _D->row_stop()))
     851             :   {
     852         194 :     if (MooseUtils::absoluteFuzzyEqual((*diag_D)(i), 0.0))
     853           0 :       mooseError("Trying to compute reciprocal of 0.");
     854         194 :     LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     855             :                       MatSetValue(dinv,
     856             :                                   i,
     857             :                                   _map_global_primary_order.at(_global_primary_dofs[i]),
     858             :                                   1.0 / (*diag_D)(i),
     859             :                                   INSERT_VALUES));
     860             :   }
     861             : 
     862          50 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     863             :                     MatAssemblyBegin(dinv, MAT_FINAL_ASSEMBLY));
     864          50 :   LibmeshPetscCallA(this->MoosePreconditioner::comm().get(),
     865             :                     MatAssemblyEnd(dinv, MAT_FINAL_ASSEMBLY));
     866          50 : }

Generated by: LCOV version 1.14