Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
nonlinear_block_solver.cpp
1 // Copyright (c) Lawrence Livermore National Security, LLC and
2 // other smith Project Developers. See the top-level LICENSE file for
3 // details.
4 //
5 // SPDX-License-Identifier: (BSD-3-Clause)
6 
13 #include "smith/physics/mesh.hpp"
14 #include "mfem.hpp"
15 
16 namespace smith {
17 
20 
22 double matrixNorm(std::unique_ptr<mfem::HypreParMatrix>& K)
23 {
24  mfem::HypreParMatrix* H = K.get();
25  hypre_ParCSRMatrix* Hhypre = static_cast<hypre_ParCSRMatrix*>(*H);
26  double Hfronorm;
27  hypre_ParCSRMatrixNormFro(Hhypre, &Hfronorm);
28  return Hfronorm;
29 }
30 
32 double skewMatrixNorm(std::unique_ptr<mfem::HypreParMatrix>& K)
33 {
34  auto K_T = std::unique_ptr<mfem::HypreParMatrix>(K->Transpose());
35  K_T->Add(-1.0, *K);
36  (*K_T) *= 0.5;
37  mfem::HypreParMatrix* H = K_T.get();
38  hypre_ParCSRMatrix* Hhypre = static_cast<hypre_ParCSRMatrix*>(*H);
39  double Hfronorm;
40  hypre_ParCSRMatrixNormFro(Hhypre, &Hfronorm);
41  return Hfronorm;
42 }
43 
44 NonlinearBlockSolver::NonlinearBlockSolver(std::unique_ptr<EquationSolver> s, MPI_Comm comm, double abs_tol,
45  double rel_tol,
46  std::optional<NonlinearSolverOptions> retained_nonlinear_options,
47  std::optional<LinearSolverOptions> retained_linear_options)
48  : nonlinear_solver_(std::move(s)),
49  comm_(comm),
50  abs_tol_(abs_tol),
51  rel_tol_(rel_tol),
52  retained_nonlinear_options_(std::move(retained_nonlinear_options)),
53  retained_linear_options_(std::move(retained_linear_options))
54 {
55 }
56 
57 std::shared_ptr<NonlinearBlockSolver> NonlinearBlockSolver::cloneFresh() const
58 {
60  return nullptr;
61  }
62 
63  auto nonlinear_opts = *retained_nonlinear_options_;
64  const auto linear_opts = *retained_linear_options_;
65 
66  auto solver = std::make_unique<EquationSolver>(nonlinear_opts, linear_opts, comm_);
67  return std::make_shared<NonlinearBlockSolver>(std::move(solver), comm_, nonlinear_opts.absolute_tol,
68  nonlinear_opts.relative_tol, nonlinear_opts, linear_opts);
69 }
70 
72  const std::vector<mfem::Vector>& residuals,
73  NonlinearConvergenceContext& context) const
74 {
75  auto block_norms = computeResidualBlockNorms(residuals, comm_);
76  return evaluateResidualConvergence(tolerance_multiplier, abs_tol_, rel_tol_, block_norms, context);
77 }
78 
79 void NonlinearBlockSolver::primeConvergenceContext(const std::vector<mfem::Vector>& residuals,
80  NonlinearConvergenceContext& context) const
81 {
82  static_cast<void>(convergenceStatus(1.0, residuals, context));
83 }
84 
85 void NonlinearBlockSolver::completeSetup(const std::vector<FieldPtr>& us) const
86 {
87  if (us.empty() || !nonlinear_solver_) return;
89 
90  // Pick the field with the highest vdim (typically the displacement field for vector problems).
91  const FieldT* best = us[0].get();
92  for (const auto& u : us) {
93  if (u->space().GetVDim() > best->space().GetVDim()) best = u.get();
94  }
95 
96  auto* mfem_solver = &nonlinear_solver_->preconditioner();
97 
98  auto* amg_prec = dynamic_cast<mfem::HypreBoomerAMG*>(mfem_solver);
99  if (amg_prec) {
100  amg_prec->SetSystemsOptions(best->space().GetVDim(), smith::ordering == mfem::Ordering::byNODES);
101  }
102 
103 #ifdef SMITH_USE_PETSC
104  auto* space_dep_pc = dynamic_cast<smith::mfem_ext::PetscPreconditionerSpaceDependent*>(mfem_solver);
105  if (space_dep_pc) {
106  // This call sets the displacement ParFiniteElementSpace used to get the spatial coordinates and to
107  // generate the near null space for the PCGAMG preconditioner
108  auto* space = const_cast<mfem::ParFiniteElementSpace*>(&best->space());
109  space_dep_pc->SetFESpace(space);
110  }
111 #endif
112 }
113 
114 std::vector<NonlinearBlockSolverBase::FieldPtr> NonlinearBlockSolver::solve(
115  const std::vector<FieldPtr>& u_guesses,
116  std::function<std::vector<mfem::Vector>(const std::vector<FieldPtr>&)> residual_funcs,
117  std::function<std::vector<std::vector<MatrixPtr>>(const std::vector<FieldPtr>&)> jacobian_funcs) const
118 {
120  SLIC_ERROR_IF(!nonlinear_solver_, "NonlinearBlockSolver requires an EquationSolver instance before solve()");
121 
122  nonlinear_solver_->resetConvergenceState();
123 
124  int num_rows = static_cast<int>(u_guesses.size());
125  SLIC_ERROR_IF(num_rows < 0, "Number of residual rows must be non-negative");
126 
127  mfem::Array<int> block_offsets;
128  block_offsets.SetSize(num_rows + 1);
129  block_offsets[0] = 0;
130  for (int row_i = 0; row_i < num_rows; ++row_i) {
131  block_offsets[row_i + 1] = u_guesses[static_cast<size_t>(row_i)]->space().TrueVSize();
132  }
133  block_offsets.PartialSum();
134  auto block_u = std::make_unique<mfem::BlockVector>(block_offsets);
135  for (int row_i = 0; row_i < num_rows; ++row_i) {
136  block_u->GetBlock(row_i) = *u_guesses[static_cast<size_t>(row_i)];
137  }
138 
139  auto block_r = std::make_unique<mfem::BlockVector>(block_offsets);
140 
141  if (!is_setup_) {
142  is_setup_ = true;
143  completeSetup(u_guesses);
144  }
145 
146  auto residual_op_ = std::make_unique<mfem_ext::StdFunctionOperator>(
147  block_u->Size(),
148  [&residual_funcs, num_rows, &u_guesses, &block_r, &block_offsets](const mfem::Vector& u_, mfem::Vector& r_) {
149  const mfem::BlockVector* u = dynamic_cast<const mfem::BlockVector*>(&u_);
150  mfem::BlockVector u_block_wrapper;
151 
152  if (!u) {
153  u_block_wrapper.Update(const_cast<double*>(u_.GetData()), block_offsets);
154  u = &u_block_wrapper;
155  }
156  for (int row_i = 0; row_i < num_rows; ++row_i) {
157  *u_guesses[static_cast<size_t>(row_i)] = u->GetBlock(row_i);
158  }
159 
160  auto residuals = residual_funcs(u_guesses);
161  SLIC_ERROR_IF(!block_r, "Invalid residual block cast to an mfem::BlockVector");
162  for (int row_i = 0; row_i < num_rows; ++row_i) {
163  auto r = residuals[static_cast<size_t>(row_i)];
164  block_r->GetBlock(row_i) = r;
165  }
166  r_ = *block_r;
167  },
168  [this, &block_offsets, &u_guesses, jacobian_funcs, num_rows](const mfem::Vector& u_) -> mfem::Operator& {
169  const mfem::BlockVector* u = dynamic_cast<const mfem::BlockVector*>(&u_);
170  mfem::BlockVector u_block_wrapper;
171  if (!u) {
172  u_block_wrapper.Update(const_cast<double*>(u_.GetData()), block_offsets);
173  u = &u_block_wrapper;
174  }
175  for (int row_i = 0; row_i < num_rows; ++row_i) {
176  *u_guesses[static_cast<size_t>(row_i)] = u->GetBlock(row_i);
177  }
178  matrix_of_jacs_ = jacobian_funcs(u_guesses);
179  if (num_rows == 1) {
180  auto& J = matrix_of_jacs_[0][0];
181  SLIC_ERROR_IF(!J, "Jacobian block (0,0) is null in single-block solve");
182  return *J;
183  }
184  block_jac_ = std::make_unique<mfem::BlockOperator>(block_offsets);
185  for (int i = 0; i < num_rows; ++i) {
186  for (int j = 0; j < num_rows; ++j) {
187  auto& J = matrix_of_jacs_[static_cast<size_t>(i)][static_cast<size_t>(j)];
188  if (J) {
189  block_jac_->SetBlock(i, j, J.get());
190  }
191  }
192  }
193  return *block_jac_;
194  });
195  nonlinear_solver_->setConvergenceTolerances(inner_tol_multiplier_ * abs_tol_, inner_tol_multiplier_ * rel_tol_,
196  comm_);
197  nonlinear_solver_->setOperator(*residual_op_);
198  nonlinear_solver_->solve(*block_u);
199 
200  for (int row_i = 0; row_i < num_rows; ++row_i) {
201  *u_guesses[static_cast<size_t>(row_i)] = block_u->GetBlock(row_i);
202  }
203 
204  return u_guesses;
205 }
206 
207 std::vector<NonlinearBlockSolverBase::FieldPtr> NonlinearBlockSolver::solveAdjoint(
208  const std::vector<DualPtr>& u_bars, std::vector<std::vector<MatrixPtr>>& jacobian_transposed) const
209 {
211 
212  int num_rows = static_cast<int>(u_bars.size());
213  SLIC_ERROR_IF(num_rows < 0, "Number of residual rows must be non-negative");
214 
215  std::vector<NonlinearBlockSolverBase::FieldPtr> u_duals(static_cast<size_t>(num_rows));
216  for (int row_i = 0; row_i < num_rows; ++row_i) {
217  u_duals[static_cast<size_t>(row_i)] = std::make_shared<NonlinearBlockSolverBase::FieldT>(
218  u_bars[static_cast<size_t>(row_i)]->space(), "u_dual_" + std::to_string(row_i));
219  }
220 
221  auto& linear_solver = nonlinear_solver_->linearSolver();
222 
223  // If it's a 1x1 system, pass the operator directly to avoid potential BlockOperator issues with smoothers
224  if (num_rows == 1) {
225  linear_solver.SetOperator(*jacobian_transposed[0][0]);
226  linear_solver.Mult(*u_bars[0], *u_duals[0]);
227  return u_duals;
228  }
229 
230  mfem::Array<int> block_offsets;
231  block_offsets.SetSize(num_rows + 1);
232  block_offsets[0] = 0;
233  for (int row_i = 0; row_i < num_rows; ++row_i) {
234  block_offsets[row_i + 1] = u_bars[static_cast<size_t>(row_i)]->space().TrueVSize();
235  }
236  block_offsets.PartialSum();
237 
238  auto block_ds = std::make_unique<mfem::BlockVector>(block_offsets);
239  *block_ds = 0.0;
240 
241  auto block_r = std::make_unique<mfem::BlockVector>(block_offsets);
242  for (int row_i = 0; row_i < num_rows; ++row_i) {
243  block_r->GetBlock(row_i) = *u_bars[static_cast<size_t>(row_i)];
244  }
245 
246  auto block_jac = std::make_unique<mfem::BlockOperator>(block_offsets);
247  for (int i = 0; i < num_rows; ++i) {
248  for (int j = 0; j < num_rows; ++j) {
249  block_jac->SetBlock(i, j, jacobian_transposed[static_cast<size_t>(i)][static_cast<size_t>(j)].get());
250  }
251  }
252 
253  linear_solver.SetOperator(*block_jac);
254  linear_solver.Mult(*block_r, *block_ds);
255 
256  for (int row_i = 0; row_i < num_rows; ++row_i) {
257  *u_duals[static_cast<size_t>(row_i)] = block_ds->GetBlock(row_i);
258  }
259 
260  return u_duals;
261 }
262 
263 std::shared_ptr<NonlinearBlockSolver> buildNonlinearBlockSolver(NonlinearSolverOptions nonlinear_opts,
264  LinearSolverOptions linear_opts,
265  const smith::Mesh& mesh)
266 {
267  auto solid_solver = std::make_unique<EquationSolver>(nonlinear_opts, linear_opts, mesh.getComm());
268  return std::make_shared<NonlinearBlockSolver>(std::move(solid_solver), mesh.getComm(), nonlinear_opts.absolute_tol,
269  nonlinear_opts.relative_tol, nonlinear_opts, linear_opts);
270 }
271 
272 } // namespace smith
This file contains the declaration of the boundary condition manager class.
Class for encapsulating the dual vector space of a finite element space (i.e. the space of linear for...
Class for encapsulating the critical MFEM components of a primal finite element field.
mfem::ParFiniteElementSpace & space()
Returns a non-owning reference to the internal FESpace.
Helper class for constructing a mesh consistent with Smith.
Definition: mesh.hpp:37
MPI_Comm getComm() const
Returns parallel communicator.
Definition: mesh.cpp:80
bool is_setup_
Records if this block solver has its preconditioner initialized.
double abs_tol_
absolute residual tolerance for convergence check
ConvergenceStatus convergenceStatus(double tolerance_multiplier, const std::vector< mfem::Vector > &residuals) const
Evaluate convergence for this solver's configured tolerance using solver-owned convergence state.
std::unique_ptr< EquationSolver > nonlinear_solver_
the nonlinear equation solver used for the forward pass
std::vector< FieldPtr > solve(const std::vector< FieldPtr > &u_guesses, std::function< std::vector< mfem::Vector >(const std::vector< FieldPtr > &)> residuals, std::function< std::vector< std::vector< MatrixPtr >>(const std::vector< FieldPtr > &)> jacobians) const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::optional< LinearSolverOptions > retained_linear_options_
retained linear config
void primeConvergenceContext(const std::vector< mfem::Vector > &residuals, NonlinearConvergenceContext &context) const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
MPI_Comm comm_
MPI communicator for parallel norm computation.
NonlinearBlockSolver(std::unique_ptr< EquationSolver > s, MPI_Comm comm, double abs_tol=1e-12, double rel_tol=1e-8, std::optional< NonlinearSolverOptions > retained_nonlinear_options=std::nullopt, std::optional< LinearSolverOptions > retained_linear_options=std::nullopt)
Construct from a nonlinear equation solver.
std::optional< NonlinearSolverOptions > retained_nonlinear_options_
retained nonlinear config
void completeSetup(const std::vector< FieldPtr > &us) const
Initialize the preconditioner in case of vector problems.
double rel_tol_
relative residual tolerance for convergence check
std::shared_ptr< NonlinearBlockSolver > cloneFresh() const
Build a fresh solver instance from retained config.
This file contains the declaration of an equation solver wrapper.
This contains a class that represents the dual of a finite element vector space, i....
This file contains the declaration of structure that manages the MFEM objects that make up the state ...
Smith mesh class which assists in constructing the appropriate parallel mfem meshes and registering a...
constexpr auto get(std::integer_sequence< int, n... >)
return the Ith integer in {n...}
Accelerator functionality.
Definition: smith.cpp:36
std::shared_ptr< NonlinearBlockSolver > buildNonlinearBlockSolver(NonlinearSolverOptions nonlinear_opts, LinearSolverOptions linear_opts, const smith::Mesh &mesh)
Create an equation-backed nonlinear block solver.
double skewMatrixNorm(std::unique_ptr< mfem::HypreParMatrix > &K)
Utility to compute 0.5*norm(K-K.T)
double matrixNorm(std::unique_ptr< mfem::HypreParMatrix > &K)
Utility to compute the matrix norm.
std::vector< double > computeResidualBlockNorms(const std::vector< mfem::Vector > &residuals, MPI_Comm comm)
Compute one L2 norm per residual block.
ConvergenceStatus evaluateResidualConvergence(double tolerance_multiplier, double abs_tol, double rel_tol, const std::vector< double > &block_norms, NonlinearConvergenceContext &context)
Evaluate scalar nonlinear residual convergence.
mfem::ParFiniteElementSpace & space(FieldState field)
Get the space from the primal field of a field states.
This file contains nonlinear block solver interfaces and helpers.
#define SMITH_MARK_FUNCTION
Definition: profiling.hpp:90
Classes for defining an mfem::Operator with std::functions.
Detailed status from evaluating nonlinear residual convergence.
Parameters for an iterative linear solution scheme.
Stores initial residual norms used for relative nonlinear convergence checks.
Nonlinear solution scheme parameters.
double relative_tol
Relative tolerance.
double absolute_tol
Absolute tolerance.