https://mooseframework.inl.gov
SegregatedSolverUtils.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 // Moose includes
11 #include "SegregatedSolverUtils.h"
12 #include "PetscVectorReader.h"
13 #include "MooseVariableFieldBase.h"
14 #include "SystemBase.h"
15 
16 // Libmesh includes
17 #include "libmesh/enum_point_locator_type.h"
18 #include "libmesh/system.h"
19 
20 namespace NS
21 {
22 namespace FV
23 {
24 void
26  const Real relaxation_parameter,
27  NumericVector<Number> & diff_diagonal)
28 {
29  PetscMatrix<Number> * mat = dynamic_cast<PetscMatrix<Number> *>(&matrix);
30  mooseAssert(mat, "This should be a PetscMatrix!");
31  PetscVector<Number> * diff_diag = dynamic_cast<PetscVector<Number> *>(&diff_diagonal);
32  mooseAssert(diff_diag, "This should be a PetscVector!");
33 
34  // Zero the diagonal difference vector
35  *diff_diag = 0;
36 
37  // Get the diagonal of the matrix
38  mat->get_diagonal(*diff_diag);
39 
40  // Create a copy of the diagonal for later use and cast it
41  auto original_diagonal = diff_diag->clone();
42 
43  // We cache the inverse of the relaxation parameter because doing divisions might
44  // be more expensive for every row
45  const Real inverse_relaxation = 1 / relaxation_parameter;
46 
47  // Now we loop over the matrix row by row and sum the absolute values of the
48  // offdiagonal values, If these sums are larger than the diagonal entry,
49  // we switch the diagonal value with the sum. At the same time we increase the
50  // diagonal by dividing it with the relaxation parameter. So the new diagonal will be:
51  // D* = 1/lambda*max(|D|,sum(|Offdiag|))
52  // For more information see
53  //
54  // Juretic, Franjo. Error analysis in finite volume CFD. Diss.
55  // Imperial College London (University of London), 2005.
56  //
57  // The trickery comes with storing everything in the diff-diagonal vector
58  // to avoid the allocation and manipulation of a third vector
59  const unsigned int local_size = matrix.row_stop() - matrix.row_start();
60  std::vector<dof_id_type> indices(local_size);
61  std::iota(indices.begin(), indices.end(), matrix.row_start());
62  std::vector<Real> new_diagonal(local_size, 0.0);
63 
64  {
65  PetscVectorReader diff_diga_reader(*diff_diag);
66  for (const auto row_i : make_range(local_size))
67  {
68  const unsigned int global_index = matrix.row_start() + row_i;
69  std::vector<numeric_index_type> indices;
70  std::vector<Real> values;
71  mat->get_row(global_index, indices, values);
72  const Real abs_sum = std::accumulate(
73  values.cbegin(), values.cend(), 0.0, [](Real a, Real b) { return a + std::abs(b); });
74  const Real abs_diagonal = std::abs(diff_diga_reader(global_index));
75  new_diagonal[row_i] = inverse_relaxation * std::max(abs_sum - abs_diagonal, abs_diagonal);
76  }
77  }
78  diff_diag->insert(new_diagonal, indices);
79 
80  // Time to modify the diagonal of the matrix. TODO: add this function to libmesh
81  LibmeshPetscCallA(mat->comm().get(), MatDiagonalSet(mat->mat(), diff_diag->vec(), INSERT_VALUES));
82  mat->close();
83  diff_diag->close();
84 
85  // Finally, we can create (D*-D) vector which is used for the relaxation of the
86  // right hand side later
87  diff_diag->add(-1.0, *original_diagonal);
88 }
89 
90 void
92  const NumericVector<Number> & solution,
93  const NumericVector<Number> & diff_diagonal)
94 {
95 
96  // We need a working vector here to make sure we don't modify the
97  // (D*-D) vector
98  auto working_vector = diff_diagonal.clone();
99  working_vector->pointwise_mult(solution, *working_vector);
100 
101  // The correction to the right hand side is just
102  // (D*-D)*old_solution
103  // For more information see
104  //
105  // Juretic, Franjo. Error analysis in finite volume CFD. Diss.
106  // Imperial College London (University of London), 2005.
107  rhs.add(*working_vector);
108  rhs.close();
109 }
110 
111 void
113  const NumericVector<Number> & vec_old,
114  const Real relaxation_factor)
115 {
116  // The relaxation is just u = lambda * u* + (1-lambda) u_old
117  vec_new.scale(relaxation_factor);
118  vec_new.add(1 - relaxation_factor, vec_old);
119  vec_new.close();
120 }
121 
122 void
123 limitSolutionUpdate(NumericVector<Number> & solution, const Real min_limit, const Real max_limit)
124 {
125  PetscVector<Number> & solution_vector = dynamic_cast<PetscVector<Number> &>(solution);
126  auto value = solution_vector.get_array();
127 
128  for (auto i : make_range(solution_vector.local_size()))
129  value[i] = std::min(std::max(min_limit, value[i]), max_limit);
130 
131  solution_vector.restore_array();
132 }
133 
134 Real
136  const SparseMatrix<Number> & mat,
137  const NumericVector<Number> & rhs)
138 {
139  // This function is based on the description provided here:
140  // @article{greenshields2022notes,
141  // title={Notes on computational fluid dynamics: General principles},
142  // author={Greenshields, Christopher J and Weller, Henry G},
143  // journal={(No Title)},
144  // year={2022}
145  // }
146  // so basically we normalize the residual with the following number:
147  // sum(|Ax-Ax_avg|+|b-Ax_avg|)
148  // where A is the system matrix, b is the system right hand side while x and x_avg are
149  // the solution and average solution vectors
150 
151  // We create a vector for Ax_avg and Ax
152  auto A_times_average_solution = solution.zero_clone();
153  auto A_times_solution = solution.zero_clone();
154 
155  // Beware, trickery here! To avoid allocating unused vectors, we
156  // first compute Ax_avg using the storage used for Ax, then we
157  // overwrite Ax with the right value
158  *A_times_solution = solution.sum() / solution.size();
159  mat.vector_mult(*A_times_average_solution, *A_times_solution);
160  mat.vector_mult(*A_times_solution, solution);
161 
162  // We create Ax-Ax_avg
163  A_times_solution->add(-1.0, *A_times_average_solution);
164  // We create Ax_avg - b (ordering shouldn't matter we will take absolute value soon)
165  A_times_average_solution->add(-1.0, rhs);
166  A_times_solution->abs();
167  A_times_average_solution->abs();
168 
169  // Create |Ax-Ax_avg|+|b-Ax_avg|
170  A_times_average_solution->add(*A_times_solution);
171 
172  // Since use the l2 norm of the solution vectors in the linear solver, we will
173  // make this consistent and use the l2 norm of the vector. We add a small number to
174  // avoid normalizing with 0.
175  // TODO: Would be nice to see if we can do l1 norms in the linear solve.
176  return (A_times_average_solution->l2_norm() + libMesh::TOLERANCE * libMesh::TOLERANCE);
177 }
178 
179 void
181  NumericVector<Number> & rhs,
182  const Real desired_value,
183  const dof_id_type dof_id)
184 {
185  // Modify the given matrix and right hand side. We use the matrix diagonal
186  // to enforce the constraint instead of 1, to make sure we don't mess up the matrix conditioning
187  // too much.
188  if (dof_id >= mx.row_start() && dof_id < mx.row_stop())
189  {
190  Real diag = mx(dof_id, dof_id);
191  rhs.add(dof_id, desired_value * diag);
192  mx.add(dof_id, dof_id, diag);
193  }
194 }
195 
197 findPointDoFID(const MooseVariableFieldBase & variable, const MooseMesh & mesh, const Point & point)
198 {
199  // Find the element containing the point
200  auto point_locator = PointLocatorBase::build(TREE_LOCAL_ELEMENTS, mesh);
201  point_locator->enable_out_of_mesh_mode();
202 
203  unsigned int var_num = variable.sys().system().variable_number(variable.name());
204 
205  // We only check in the restricted blocks, if needed
206  const bool block_restricted =
207  variable.blockIDs().find(Moose::ANY_BLOCK_ID) == variable.blockIDs().end();
208  const Elem * elem =
209  block_restricted ? (*point_locator)(point, &variable.blockIDs()) : (*point_locator)(point);
210 
211  // We communicate the results and if there is conflict between processes,
212  // the minimum cell ID is chosen
213  const dof_id_type elem_id = elem ? elem->id() : DofObject::invalid_id;
214  dof_id_type min_elem_id = elem_id;
215  variable.sys().comm().min(min_elem_id);
216 
217  if (min_elem_id == DofObject::invalid_id)
218  mooseError("Variable ",
219  variable.name(),
220  " is not defined at ",
221  Moose::stringify(point),
222  "! Try alleviating block restrictions or using another point!");
223 
224  return min_elem_id == elem_id ? elem->dof_number(variable.sys().number(), var_num, 0)
225  : DofObject::invalid_id;
226 }
227 
228 bool
229 converged(const std::vector<std::pair<unsigned int, Real>> & its_and_residuals,
230  const std::vector<Real> & abs_tolerances)
231 {
232  mooseAssert(its_and_residuals.size() == abs_tolerances.size(),
233  "The number of residuals should (now " + std::to_string(its_and_residuals.size()) +
234  ") be the same as the number of tolerances (" +
235  std::to_string(abs_tolerances.size()) + ")!");
236 
237  bool converged = true;
238  for (const auto system_i : index_range(its_and_residuals))
239  {
240  converged = converged && (its_and_residuals[system_i].second < abs_tolerances[system_i]);
241  if (!converged)
242  return converged;
243  }
244  return converged;
245 }
246 
247 } // End FV namespace
248 } // End Moose namespace
void relaxMatrix(SparseMatrix< Number > &matrix_in, const Real relaxation_parameter, NumericVector< Number > &diff_diagonal)
Relax the matrix to ensure diagonal dominance, we hold onto the difference in diagonals for later use...
void mooseError(Args &&... args)
Real computeNormalizationFactor(const NumericVector< Number > &solution, const SparseMatrix< Number > &mat, const NumericVector< Number > &rhs)
Compute a normalization factor which is applied to the linear residual to determine convergence...
static constexpr Real TOLERANCE
void vector_mult(NumericVector< Number > &dest, const NumericVector< Number > &arg) const
virtual Number sum() const =0
virtual numeric_index_type size() const =0
virtual std::unique_ptr< NumericVector< Number > > zero_clone() const =0
virtual libMesh::System & system()=0
MeshBase & mesh
const std::string & name() const override
const Parallel::Communicator & comm() const
virtual std::unique_ptr< NumericVector< Number > > clone() const =0
virtual const std::set< SubdomainID > & blockIDs() const
void relaxRightHandSide(NumericVector< Number > &rhs_in, const NumericVector< Number > &solution_in, const NumericVector< Number > &diff_diagonal)
Relax the right hand side of an equation, this needs to be called once and the system matrix has been...
virtual numeric_index_type row_stop() const =0
unsigned int variable_number(std::string_view var) const
virtual void add(const numeric_index_type i, const numeric_index_type j, const Number value)=0
bool converged(const std::vector< std::pair< unsigned int, Real >> &residuals, const std::vector< Real > &abs_tolerances)
Based on the residuals, determine if the iterative process converged or not.
virtual void scale(const Number factor)=0
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
void min(const T &r, T &o, Request &req) const
unsigned int number() const
std::string stringify(const T &t)
dof_id_type findPointDoFID(const MooseVariableFieldBase &variable, const MooseMesh &mesh, const Point &point)
Find the ID of the degree of freedom which corresponds to the variable and a given point on the mesh...
virtual void close()=0
const SubdomainID ANY_BLOCK_ID
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual numeric_index_type row_start() const =0
IntRange< T > make_range(T beg, T end)
TREE_LOCAL_ELEMENTS
void constrainSystem(SparseMatrix< Number > &mx, NumericVector< Number > &rhs, const Real desired_value, const dof_id_type dof_id)
Implicitly constrain the system by adding a factor*(u-u_desired) to it at a desired dof value...
virtual void add(const numeric_index_type i, const Number value)=0
void limitSolutionUpdate(NumericVector< Number > &solution, const Real min_limit=std::numeric_limits< Real >::epsilon(), const Real max_limit=1e10)
Limit a solution to its minimum and maximum bounds: $u = min(max(u, min_limit), max_limit)$.
SystemBase & sys()
void relaxSolutionUpdate(NumericVector< Number > &vec_new, const NumericVector< Number > &vec_old, const Real relaxation_factor)
Relax the update on a solution field using the following approach: $u = u_{old}+ (u - u_{old})$...
auto index_range(const T &sizable)
uint8_t dof_id_type