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 
71 void NonlinearBlockSolver::completeSetup(const std::vector<FieldPtr>& us) const
72 {
73  if (us.empty() || !nonlinear_solver_) return;
75 
76  // Pick the field with the highest vdim (typically the displacement field for vector problems).
77  SLIC_ERROR_IF(!us[0], "NonlinearBlockSolver::completeSetup received a null field");
78  const FieldT* best = us[0].get();
79  for (const auto& u : us) {
80  SLIC_ERROR_IF(!u, "NonlinearBlockSolver::completeSetup received a null field");
81  if (u->space().GetVDim() > best->space().GetVDim()) best = u.get();
82  }
83 
84  auto* mfem_solver = &nonlinear_solver_->preconditioner();
85 
86  auto* amg_prec = dynamic_cast<mfem::HypreBoomerAMG*>(mfem_solver);
87  if (amg_prec) {
88  amg_prec->SetSystemsOptions(best->space().GetVDim(), smith::ordering == mfem::Ordering::byNODES);
89  }
90 
91 #ifdef SMITH_USE_PETSC
92  auto* space_dep_pc = dynamic_cast<smith::mfem_ext::PetscPreconditionerSpaceDependent*>(mfem_solver);
93  if (space_dep_pc) {
94  // This call sets the displacement ParFiniteElementSpace used to get the spatial coordinates and to
95  // generate the near null space for the PCGAMG preconditioner
96  auto* space = const_cast<mfem::ParFiniteElementSpace*>(&best->space());
97  space_dep_pc->SetFESpace(space);
98  }
99 #endif
100 }
101 
103  const std::vector<mfem::Vector>& residuals,
104  NonlinearConvergenceContext& context) const
105 {
106  auto block_norms = computeResidualBlockNorms(residuals, comm_);
107  return evaluateResidualConvergence(tolerance_multiplier, abs_tol_, rel_tol_, block_norms, context);
108 }
109 
110 void NonlinearBlockSolver::primeConvergenceContext(const std::vector<mfem::Vector>& residuals,
111  NonlinearConvergenceContext& context) const
112 {
113  static_cast<void>(convergenceStatus(1.0, residuals, context));
114 }
115 
116 std::vector<NonlinearBlockSolverBase::FieldPtr> NonlinearBlockSolver::solve(
117  const std::vector<FieldPtr>& u_guesses,
118  std::function<std::vector<mfem::Vector>(const std::vector<FieldPtr>&)> residual_funcs,
119  std::function<std::vector<std::vector<MatrixPtr>>(const std::vector<FieldPtr>&)> jacobian_funcs) const
120 {
122  SLIC_ERROR_IF(!nonlinear_solver_, "NonlinearBlockSolver requires an EquationSolver instance before solve()");
123 
124  nonlinear_solver_->resetConvergenceState();
125 
126  int num_rows = static_cast<int>(u_guesses.size());
127  SLIC_ERROR_IF(num_rows < 0, "Number of residual rows must be non-negative");
128 
129  mfem::Array<int> block_offsets;
130  block_offsets.SetSize(num_rows + 1);
131  block_offsets[0] = 0;
132  for (int row_i = 0; row_i < num_rows; ++row_i) {
133  block_offsets[row_i + 1] = u_guesses[static_cast<size_t>(row_i)]->space().TrueVSize();
134  }
135  block_offsets.PartialSum();
136  auto block_u = std::make_unique<mfem::BlockVector>(block_offsets);
137  for (int row_i = 0; row_i < num_rows; ++row_i) {
138  block_u->GetBlock(row_i) = *u_guesses[static_cast<size_t>(row_i)];
139  }
140 
141  auto block_r = std::make_unique<mfem::BlockVector>(block_offsets);
142 
143  if (!is_setup_) {
144  is_setup_ = true;
145  completeSetup(u_guesses);
146  }
147 
148  auto residual_op_ = std::make_unique<mfem_ext::StdFunctionOperator>(
149  block_u->Size(),
150  [&residual_funcs, num_rows, &u_guesses, &block_r, &block_offsets](const mfem::Vector& u_, mfem::Vector& r_) {
151  const mfem::BlockVector* u = dynamic_cast<const mfem::BlockVector*>(&u_);
152  mfem::BlockVector u_block_wrapper;
153 
154  if (!u) {
155  u_block_wrapper.Update(const_cast<double*>(u_.GetData()), block_offsets);
156  u = &u_block_wrapper;
157  }
158  for (int row_i = 0; row_i < num_rows; ++row_i) {
159  *u_guesses[static_cast<size_t>(row_i)] = u->GetBlock(row_i);
160  }
161 
162  auto residuals = residual_funcs(u_guesses);
163  SLIC_ERROR_IF(!block_r, "Invalid residual block cast to an mfem::BlockVector");
164  for (int row_i = 0; row_i < num_rows; ++row_i) {
165  auto r = residuals[static_cast<size_t>(row_i)];
166  block_r->GetBlock(row_i) = r;
167  }
168  r_ = *block_r;
169  },
170  [this, &block_offsets, &u_guesses, jacobian_funcs, num_rows](const mfem::Vector& u_) -> mfem::Operator& {
171  const mfem::BlockVector* u = dynamic_cast<const mfem::BlockVector*>(&u_);
172  mfem::BlockVector u_block_wrapper;
173  if (!u) {
174  u_block_wrapper.Update(const_cast<double*>(u_.GetData()), block_offsets);
175  u = &u_block_wrapper;
176  }
177  for (int row_i = 0; row_i < num_rows; ++row_i) {
178  *u_guesses[static_cast<size_t>(row_i)] = u->GetBlock(row_i);
179  }
180  matrix_of_jacs_ = jacobian_funcs(u_guesses);
181  if (num_rows == 1) {
182  auto& J = matrix_of_jacs_[0][0];
183  SLIC_ERROR_IF(!J, "Jacobian block (0,0) is null in single-block solve");
184  return *J;
185  }
186  block_jac_ = std::make_unique<mfem::BlockOperator>(block_offsets);
187  for (int i = 0; i < num_rows; ++i) {
188  for (int j = 0; j < num_rows; ++j) {
189  auto& J = matrix_of_jacs_[static_cast<size_t>(i)][static_cast<size_t>(j)];
190  if (J) {
191  block_jac_->SetBlock(i, j, J.get());
192  }
193  }
194  }
195  return *block_jac_;
196  });
197  nonlinear_solver_->setConvergenceTolerances(inner_tol_multiplier_ * abs_tol_, inner_tol_multiplier_ * rel_tol_,
198  comm_);
199  nonlinear_solver_->setOperator(*residual_op_);
200  nonlinear_solver_->solve(*block_u);
201 
202  for (int row_i = 0; row_i < num_rows; ++row_i) {
203  *u_guesses[static_cast<size_t>(row_i)] = block_u->GetBlock(row_i);
204  }
205 
206  return u_guesses;
207 }
208 
209 std::vector<NonlinearBlockSolverBase::FieldPtr> NonlinearBlockSolver::solveAdjoint(
210  const std::vector<DualPtr>& u_bars, std::vector<std::vector<MatrixPtr>>& jacobian_transposed) const
211 {
213 
214  int num_rows = static_cast<int>(u_bars.size());
215  SLIC_ERROR_IF(num_rows < 0, "Number of residual rows must be non-negative");
216 
217  std::vector<NonlinearBlockSolverBase::FieldPtr> u_duals(static_cast<size_t>(num_rows));
218  for (int row_i = 0; row_i < num_rows; ++row_i) {
219  u_duals[static_cast<size_t>(row_i)] = std::make_shared<NonlinearBlockSolverBase::FieldT>(
220  u_bars[static_cast<size_t>(row_i)]->space(), "u_dual_" + std::to_string(row_i));
221  }
222 
223  auto& linear_solver = nonlinear_solver_->linearSolver();
224 
225  // If it's a 1x1 system, pass the operator directly to avoid potential BlockOperator issues with smoothers
226  if (num_rows == 1) {
227  linear_solver.SetOperator(*jacobian_transposed[0][0]);
228  linear_solver.Mult(*u_bars[0], *u_duals[0]);
229  return u_duals;
230  }
231 
232  mfem::Array<int> block_offsets;
233  block_offsets.SetSize(num_rows + 1);
234  block_offsets[0] = 0;
235  for (int row_i = 0; row_i < num_rows; ++row_i) {
236  block_offsets[row_i + 1] = u_bars[static_cast<size_t>(row_i)]->space().TrueVSize();
237  }
238  block_offsets.PartialSum();
239 
240  auto block_ds = std::make_unique<mfem::BlockVector>(block_offsets);
241  *block_ds = 0.0;
242 
243  auto block_r = std::make_unique<mfem::BlockVector>(block_offsets);
244  for (int row_i = 0; row_i < num_rows; ++row_i) {
245  block_r->GetBlock(row_i) = *u_bars[static_cast<size_t>(row_i)];
246  }
247 
248  auto block_jac = std::make_unique<mfem::BlockOperator>(block_offsets);
249  for (int i = 0; i < num_rows; ++i) {
250  for (int j = 0; j < num_rows; ++j) {
251  auto& J = jacobian_transposed[static_cast<size_t>(i)][static_cast<size_t>(j)];
252  if (J) {
253  block_jac->SetBlock(i, j, J.get());
254  }
255  }
256  }
257 
258  linear_solver.SetOperator(*block_jac);
259  linear_solver.Mult(*block_r, *block_ds);
260 
261  for (int row_i = 0; row_i < num_rows; ++row_i) {
262  *u_duals[static_cast<size_t>(row_i)] = block_ds->GetBlock(row_i);
263  }
264 
265  return u_duals;
266 }
267 
268 std::shared_ptr<NonlinearBlockSolver> buildNonlinearBlockSolver(NonlinearSolverOptions nonlinear_opts,
269  LinearSolverOptions linear_opts,
270  const smith::Mesh& mesh)
271 {
272  auto solid_solver = std::make_unique<EquationSolver>(nonlinear_opts, linear_opts, mesh.getComm());
273  return std::make_shared<NonlinearBlockSolver>(std::move(solid_solver), mesh.getComm(), nonlinear_opts.absolute_tol,
274  nonlinear_opts.relative_tol, nonlinear_opts, linear_opts);
275 }
276 
277 } // 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...
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.