Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
differentiable_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 
14 #include "smith/physics/mesh.hpp"
15 #include "mfem.hpp"
16 
17 namespace smith {
18 
21 
23 double matrixNorm(std::unique_ptr<mfem::HypreParMatrix>& K)
24 {
25  mfem::HypreParMatrix* H = K.get();
26  hypre_ParCSRMatrix* Hhypre = static_cast<hypre_ParCSRMatrix*>(*H);
27  double Hfronorm;
28  hypre_ParCSRMatrixNormFro(Hhypre, &Hfronorm);
29  return Hfronorm;
30 }
31 
33 double skewMatrixNorm(std::unique_ptr<mfem::HypreParMatrix>& K)
34 {
35  auto K_T = std::unique_ptr<mfem::HypreParMatrix>(K->Transpose());
36  K_T->Add(-1.0, *K);
37  (*K_T) *= 0.5;
38  mfem::HypreParMatrix* H = K_T.get();
39  hypre_ParCSRMatrix* Hhypre = static_cast<hypre_ParCSRMatrix*>(*H);
40  double Hfronorm;
41  hypre_ParCSRMatrixNormFro(Hhypre, &Hfronorm);
42  return Hfronorm;
43 }
44 
46 void initializeSolver(mfem::Solver* mfem_solver, const smith::FiniteElementState& u)
47 {
48  // If the user wants the AMG preconditioner with a linear solver, set the pfes
49  // to be the displacement
50  auto* amg_prec = dynamic_cast<mfem::HypreBoomerAMG*>(mfem_solver);
51  if (amg_prec) {
52  amg_prec->SetSystemsOptions(u.space().GetVDim(), smith::ordering == mfem::Ordering::byNODES);
53  }
54 
55 #ifdef SMITH_USE_PETSC
56  auto* space_dep_pc = dynamic_cast<smith::mfem_ext::PetscPreconditionerSpaceDependent*>(mfem_solver);
57  if (space_dep_pc) {
58  // This call sets the displacement ParFiniteElementSpace used to get the spatial coordinates and to
59  // generate the near null space for the PCGAMG preconditioner
60  mfem::ParFiniteElementSpace* space = const_cast<mfem::ParFiniteElementSpace*>(&u.space());
61  space_dep_pc->SetFESpace(space);
62  }
63 #endif
64 }
65 
66 LinearDifferentiableSolver::LinearDifferentiableSolver(std::unique_ptr<mfem::Solver> s, std::unique_ptr<mfem::Solver> p)
67  : mfem_solver(std::move(s)), mfem_preconditioner(std::move(p))
68 {
69 }
70 
72 {
74 }
75 
76 std::shared_ptr<FiniteElementState> LinearDifferentiableSolver::solve(
77  const FiniteElementState& u, // initial guess
78  std::function<mfem::Vector(const FiniteElementState&)> equation,
79  std::function<std::unique_ptr<mfem::HypreParMatrix>(const FiniteElementState&)> jacobian) const
80 {
82  auto r = equation(u);
83  auto du = std::make_shared<FiniteElementState>(u.space(), "u");
84  *du = 0.0;
85  auto Jptr = jacobian(u);
86  mfem_solver->SetOperator(*Jptr);
87  mfem_solver->Mult(r, *du);
88  *du -= u;
89  *du *= -1.0;
90  return du; // return u - K^{-1}r
91 }
92 
93 std::shared_ptr<FiniteElementState> LinearDifferentiableSolver::solveAdjoint(
94  const FiniteElementDual& u_bar, std::unique_ptr<mfem::HypreParMatrix> jacobian_transposed) const
95 {
97 
98  auto ds = std::make_shared<FiniteElementState>(u_bar.space(), "ds");
99  mfem_solver->SetOperator(*jacobian_transposed);
100  mfem_solver->Mult(u_bar, *ds);
101  return ds;
102 }
103 
105  : nonlinear_solver_(std::move(s))
106 {
107 }
108 
110 {
111  initializeSolver(&nonlinear_solver_->preconditioner(), u);
112 }
113 
114 std::shared_ptr<FiniteElementState> NonlinearDifferentiableSolver::solve(
115  const FiniteElementState& u_guess, // initial guess
116  std::function<mfem::Vector(const FiniteElementState&)> equation,
117  std::function<std::unique_ptr<mfem::HypreParMatrix>(const FiniteElementState&)> jacobian) const
118 {
120 
121  auto u = std::make_shared<FiniteElementState>(u_guess);
122 
123  auto residual_op_ = std::make_unique<mfem_ext::StdFunctionOperator>(
124  u->space().TrueVSize(),
125 
126  [&u, &equation](const mfem::Vector& u_, mfem::Vector& r_) {
127  FiniteElementState uu(u->space(), "uu");
128  uu = u_;
129  r_ = equation(uu);
130  },
131 
132  [&u, &jacobian, this](const mfem::Vector& u_) -> mfem::Operator& {
133  FiniteElementState uu(u->space(), "uu");
134  uu = u_;
135  J_.reset();
136  J_ = jacobian(uu);
137  return *J_;
138  });
139 
140  nonlinear_solver_->setOperator(*residual_op_);
141  nonlinear_solver_->solve(*u);
142 
143  return u;
144 }
145 
146 std::shared_ptr<FiniteElementState> NonlinearDifferentiableSolver::solveAdjoint(
147  const FiniteElementDual& x_bar, std::unique_ptr<mfem::HypreParMatrix> jacobian_transposed) const
148 {
150 
151  auto ds = std::make_shared<FiniteElementState>(x_bar.space(), "ds");
152  auto& linear_solver = nonlinear_solver_->linearSolver();
153  linear_solver.SetOperator(*jacobian_transposed);
154  linear_solver.Mult(x_bar, *ds);
155 
156  return ds;
157 }
158 
160 
162  std::unique_ptr<mfem::Solver> p)
163  : mfem_solver(std::move(s)), mfem_preconditioner(std::move(p))
164 {
165 }
166 
167 void LinearDifferentiableBlockSolver::completeSetup(const std::vector<FieldT>& us)
168 {
170 }
171 
172 std::vector<DifferentiableBlockSolver::FieldPtr> LinearDifferentiableBlockSolver::solve(
173  const std::vector<FieldPtr>& u_guesses,
174  std::function<std::vector<mfem::Vector>(const std::vector<FieldPtr>&)> residual_funcs,
175  std::function<std::vector<std::vector<MatrixPtr>>(const std::vector<FieldPtr>&)> jacobian_funcs) const
176 {
178 
179  int num_rows = static_cast<int>(u_guesses.size());
180  SLIC_ERROR_IF(num_rows < 0, "Number of residual rows must be non-negative");
181 
182  mfem::Array<int> block_offsets;
183  block_offsets.SetSize(num_rows + 1);
184  block_offsets[0] = 0;
185  for (int row_i = 0; row_i < num_rows; ++row_i) {
186  block_offsets[row_i + 1] = u_guesses[static_cast<size_t>(row_i)]->space().TrueVSize();
187  }
188  block_offsets.PartialSum();
189 
190  auto block_du = std::make_unique<mfem::BlockVector>(block_offsets);
191  for (int row_i = 0; row_i < num_rows; ++row_i) {
192  block_du->GetBlock(row_i) = *u_guesses[static_cast<size_t>(row_i)];
193  }
194 
195  auto residuals = residual_funcs(u_guesses);
196  auto block_r = std::make_unique<mfem::BlockVector>(block_offsets);
197  for (int row_i = 0; row_i < num_rows; ++row_i) {
198  block_r->GetBlock(row_i) = residuals[static_cast<size_t>(row_i)];
199  }
200 
201  auto jacs = jacobian_funcs(u_guesses);
202  auto block_jac = std::make_unique<mfem::BlockOperator>(block_offsets);
203  for (int i = 0; i < num_rows; ++i) {
204  for (int j = 0; j < num_rows; ++j) {
205  block_jac->SetBlock(i, j, jacs[static_cast<size_t>(i)][static_cast<size_t>(j)].get());
206  }
207  }
208 
209  mfem_solver->SetOperator(*block_jac);
210 
211  mfem_solver->Mult(*block_r, *block_du);
212 
213  for (int row_i = 0; row_i < num_rows; ++row_i) {
214  *u_guesses[static_cast<size_t>(row_i)] -= block_du->GetBlock(row_i);
215  }
216  *block_du = 0.0;
217 
218  return u_guesses;
219 }
220 
221 std::vector<DifferentiableBlockSolver::FieldPtr> LinearDifferentiableBlockSolver::solveAdjoint(
222  const std::vector<DualPtr>& u_bars, std::vector<std::vector<MatrixPtr>>& jacobian_transposed) const
223 {
225 
226  int num_rows = static_cast<int>(u_bars.size());
227  SLIC_ERROR_IF(num_rows < 0, "Number of residual rows must be non-negative");
228 
229  std::vector<DifferentiableBlockSolver::FieldPtr> u_duals(static_cast<size_t>(num_rows));
230  for (int row_i = 0; row_i < num_rows; ++row_i) {
231  u_duals[static_cast<size_t>(row_i)] = std::make_shared<DifferentiableBlockSolver::FieldT>(
232  u_bars[static_cast<size_t>(row_i)]->space(), "u_dual_" + std::to_string(row_i));
233  }
234 
235  mfem::Array<int> block_offsets;
236  block_offsets.SetSize(num_rows + 1);
237  block_offsets[0] = 0;
238  for (int row_i = 0; row_i < num_rows; ++row_i) {
239  block_offsets[row_i + 1] = u_bars[static_cast<size_t>(row_i)]->space().TrueVSize();
240  }
241  block_offsets.PartialSum();
242 
243  auto block_ds = std::make_unique<mfem::BlockVector>(block_offsets);
244  *block_ds = 0.0;
245 
246  auto block_r = std::make_unique<mfem::BlockVector>(block_offsets);
247  for (int row_i = 0; row_i < num_rows; ++row_i) {
248  block_r->GetBlock(row_i) = *u_bars[static_cast<size_t>(row_i)];
249  }
250 
251  auto block_jac = std::make_unique<mfem::BlockOperator>(block_offsets);
252  for (int i = 0; i < num_rows; ++i) {
253  for (int j = 0; j < num_rows; ++j) {
254  block_jac->SetBlock(i, j, jacobian_transposed[static_cast<size_t>(i)][static_cast<size_t>(j)].get());
255  }
256  }
257 
258  mfem_solver->SetOperator(*block_jac);
259 
260  mfem_solver->Mult(*block_r, *block_ds);
261 
262  for (int row_i = 0; row_i < num_rows; ++row_i) {
263  *u_duals[static_cast<size_t>(row_i)] = block_ds->GetBlock(row_i);
264  }
265 
266  return u_duals;
267 }
268 
270  : nonlinear_solver_(std::move(s))
271 {
272 }
273 
275 {
276  // TODO: eventually may need something like: initializeSolver(&nonlinear_solver_->preconditioner(), u);
277 }
278 
279 std::vector<DifferentiableBlockSolver::FieldPtr> NonlinearDifferentiableBlockSolver::solve(
280  const std::vector<FieldPtr>& u_guesses,
281  std::function<std::vector<mfem::Vector>(const std::vector<FieldPtr>&)> residual_funcs,
282  std::function<std::vector<std::vector<MatrixPtr>>(const std::vector<FieldPtr>&)> jacobian_funcs) const
283 {
285 
286  int num_rows = static_cast<int>(u_guesses.size());
287  SLIC_ERROR_IF(num_rows < 0, "Number of residual rows must be non-negative");
288 
289  mfem::Array<int> block_offsets;
290  block_offsets.SetSize(num_rows + 1);
291  block_offsets[0] = 0;
292  for (int row_i = 0; row_i < num_rows; ++row_i) {
293  block_offsets[row_i + 1] = u_guesses[static_cast<size_t>(row_i)]->space().TrueVSize();
294  }
295  block_offsets.PartialSum();
296 
297  auto block_u = std::make_unique<mfem::BlockVector>(block_offsets);
298  for (int row_i = 0; row_i < num_rows; ++row_i) {
299  block_u->GetBlock(row_i) = *u_guesses[static_cast<size_t>(row_i)];
300  }
301 
302  auto block_r = std::make_unique<mfem::BlockVector>(block_offsets);
303 
304  auto residual_op_ = std::make_unique<mfem_ext::StdFunctionOperator>(
305  block_u->Size(),
306  [&residual_funcs, num_rows, &u_guesses, &block_r](const mfem::Vector& u_, mfem::Vector& r_) {
307  const mfem::BlockVector* u = dynamic_cast<const mfem::BlockVector*>(&u_);
308  SLIC_ERROR_IF(!u, "Invalid u cast in block differentiable solver to a block vector");
309  for (int row_i = 0; row_i < num_rows; ++row_i) {
310  *u_guesses[static_cast<size_t>(row_i)] = u->GetBlock(row_i);
311  }
312  auto residuals = residual_funcs(u_guesses);
313  SLIC_ERROR_IF(!block_r, "Invalid r cast in block differentiable solver to a block vector");
314  for (int row_i = 0; row_i < num_rows; ++row_i) {
315  auto r = residuals[static_cast<size_t>(row_i)];
316  block_r->GetBlock(row_i) = r;
317  }
318  r_ = *block_r;
319  },
320  [this, &block_offsets, &u_guesses, jacobian_funcs, num_rows](const mfem::Vector& u_) -> mfem::Operator& {
321  const mfem::BlockVector* u = dynamic_cast<const mfem::BlockVector*>(&u_);
322  SLIC_ERROR_IF(!u, "Invalid u cast in block differentiable solver to a block vector");
323  for (int row_i = 0; row_i < num_rows; ++row_i) {
324  *u_guesses[static_cast<size_t>(row_i)] = u->GetBlock(row_i);
325  }
326  block_jac_ = std::make_unique<mfem::BlockOperator>(block_offsets);
327  matrix_of_jacs_ = jacobian_funcs(u_guesses);
328  for (int i = 0; i < num_rows; ++i) {
329  for (int j = 0; j < num_rows; ++j) {
330  auto& J = matrix_of_jacs_[static_cast<size_t>(i)][static_cast<size_t>(j)];
331  if (J) {
332  block_jac_->SetBlock(i, j, J.get());
333  }
334  }
335  }
336  return *block_jac_;
337  });
338  nonlinear_solver_->setOperator(*residual_op_);
339  nonlinear_solver_->solve(*block_u);
340 
341  for (int row_i = 0; row_i < num_rows; ++row_i) {
342  *u_guesses[static_cast<size_t>(row_i)] = block_u->GetBlock(row_i);
343  }
344 
345  return u_guesses;
346 }
347 
348 std::vector<DifferentiableBlockSolver::FieldPtr> NonlinearDifferentiableBlockSolver::solveAdjoint(
349  const std::vector<DualPtr>& u_bars, std::vector<std::vector<MatrixPtr>>& jacobian_transposed) const
350 {
352 
353  int num_rows = static_cast<int>(u_bars.size());
354  SLIC_ERROR_IF(num_rows < 0, "Number of residual rows must be non-negative");
355 
356  std::vector<DifferentiableBlockSolver::FieldPtr> u_duals(static_cast<size_t>(num_rows));
357  for (int row_i = 0; row_i < num_rows; ++row_i) {
358  u_duals[static_cast<size_t>(row_i)] = std::make_shared<DifferentiableBlockSolver::FieldT>(
359  u_bars[static_cast<size_t>(row_i)]->space(), "u_dual_" + std::to_string(row_i));
360  }
361 
362  mfem::Array<int> block_offsets;
363  block_offsets.SetSize(num_rows + 1);
364  block_offsets[0] = 0;
365  for (int row_i = 0; row_i < num_rows; ++row_i) {
366  block_offsets[row_i + 1] = u_bars[static_cast<size_t>(row_i)]->space().TrueVSize();
367  }
368  block_offsets.PartialSum();
369 
370  auto block_ds = std::make_unique<mfem::BlockVector>(block_offsets);
371  *block_ds = 0.0;
372 
373  auto block_r = std::make_unique<mfem::BlockVector>(block_offsets);
374  for (int row_i = 0; row_i < num_rows; ++row_i) {
375  block_r->GetBlock(row_i) = *u_bars[static_cast<size_t>(row_i)];
376  }
377 
378  auto block_jac = std::make_unique<mfem::BlockOperator>(block_offsets);
379  for (int i = 0; i < num_rows; ++i) {
380  for (int j = 0; j < num_rows; ++j) {
381  block_jac->SetBlock(i, j, jacobian_transposed[static_cast<size_t>(i)][static_cast<size_t>(j)].get());
382  }
383  }
384 
385  auto& linear_solver = nonlinear_solver_->linearSolver();
386  linear_solver.SetOperator(*block_jac);
387  linear_solver.Mult(*block_r, *block_ds);
388 
389  for (int row_i = 0; row_i < num_rows; ++row_i) {
390  *u_duals[static_cast<size_t>(row_i)] = block_ds->GetBlock(row_i);
391  }
392 
393  return u_duals;
394 }
395 
396 std::shared_ptr<LinearDifferentiableSolver> buildDifferentiableLinearSolver(LinearSolverOptions linear_opts,
397  const smith::Mesh& mesh)
398 {
399  auto [linear_solver, precond] = smith::buildLinearSolverAndPreconditioner(linear_opts, mesh.getComm());
400  return std::make_shared<smith::LinearDifferentiableSolver>(std::move(linear_solver), std::move(precond));
401 }
402 
403 std::shared_ptr<NonlinearDifferentiableSolver> buildDifferentiableNonlinearSolver(NonlinearSolverOptions nonlinear_opts,
404  LinearSolverOptions linear_opts,
405  const smith::Mesh& mesh)
406 {
407  auto solid_solver = std::make_unique<EquationSolver>(nonlinear_opts, linear_opts, mesh.getComm());
408  return std::make_shared<NonlinearDifferentiableSolver>(std::move(solid_solver));
409 }
410 
411 std::shared_ptr<NonlinearDifferentiableBlockSolver> buildDifferentiableNonlinearBlockSolver(
412  NonlinearSolverOptions nonlinear_opts, LinearSolverOptions linear_opts, const smith::Mesh& mesh)
413 {
414  auto solid_solver = std::make_unique<EquationSolver>(nonlinear_opts, linear_opts, mesh.getComm());
415  return std::make_shared<NonlinearDifferentiableBlockSolver>(std::move(solid_solver));
416 }
417 
418 } // 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.
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::unique_ptr< mfem::Solver > mfem_preconditioner
stored mfem block preconditioner
std::vector< FieldPtr > solveAdjoint(const std::vector< DualPtr > &u_bars, std::vector< std::vector< MatrixPtr >> &jacobian_transposed) const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::unique_ptr< mfem::Solver > mfem_solver
stored mfem block solver
LinearDifferentiableBlockSolver(std::unique_ptr< mfem::Solver > s, std::unique_ptr< mfem::Solver > p)
Construct from a linear solver and linear block precondition which may be used by the linear solver.
void completeSetup(const std::vector< FieldT > &us) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::unique_ptr< mfem::Solver > mfem_preconditioner
optionally used preconditioner
std::shared_ptr< smith::FiniteElementState > solve(const smith::FiniteElementState &u_guess, std::function< mfem::Vector(const smith::FiniteElementState &)> equation, std::function< std::unique_ptr< mfem::HypreParMatrix >(const smith::FiniteElementState &)> jacobian) const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
void completeSetup(const smith::FiniteElementState &u) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::unique_ptr< mfem::Solver > mfem_solver
linear solver
LinearDifferentiableSolver(std::unique_ptr< mfem::Solver > s, std::unique_ptr< mfem::Solver > p)
Construct from a linear solver and linear precondition which may also be used by a nonlinear solver.
std::shared_ptr< smith::FiniteElementState > solveAdjoint(const smith::FiniteElementDual &u_bar, std::unique_ptr< mfem::HypreParMatrix > jacobian_transposed) const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
Helper class for constructing a mesh consistent with Smith.
Definition: mesh.hpp:37
MPI_Comm getComm() const
Returns parallel communicator.
Definition: mesh.cpp:79
void completeSetup(const std::vector< FieldT > &us) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
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...
NonlinearDifferentiableBlockSolver(std::unique_ptr< EquationSolver > s)
Construct from a linear solver and linear block precondition which may be used by the linear solver.
std::shared_ptr< smith::FiniteElementState > solveAdjoint(const smith::FiniteElementDual &u_bar, std::unique_ptr< mfem::HypreParMatrix > jacobian_transposed) const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
void completeSetup(const smith::FiniteElementState &u) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::unique_ptr< mfem::HypreParMatrix > J_
stored linearized Jacobian matrix for memory reuse
void clearMemory() const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
NonlinearDifferentiableSolver(std::unique_ptr< EquationSolver > s)
Consruct from a smith nonlinear EquationSolver.
std::unique_ptr< EquationSolver > nonlinear_solver_
the nonlinear equation solver used for the forward pass
std::shared_ptr< smith::FiniteElementState > solve(const smith::FiniteElementState &u_guess, std::function< mfem::Vector(const smith::FiniteElementState &)> equation, std::function< std::unique_ptr< mfem::HypreParMatrix >(const smith::FiniteElementState &)> jacobian) const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
This file contains the declaration of the DifferentiableSolver interface.
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< NonlinearDifferentiableSolver > buildDifferentiableNonlinearSolver(NonlinearSolverOptions nonlinear_opts, LinearSolverOptions linear_opts, const smith::Mesh &mesh)
Create a differentiable nonlinear solver.
std::pair< std::unique_ptr< mfem::Solver >, std::unique_ptr< mfem::Solver > > buildLinearSolverAndPreconditioner(LinearSolverOptions linear_opts, MPI_Comm comm)
Build the linear solver and its associated preconditioner given a linear options struct.
constexpr T & get(variant< T0, T1 > &v)
Returns the variant member of specified type.
Definition: variant.hpp:338
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.
mfem::ParFiniteElementSpace & space(FieldState field)
Get the space from the primal field of a field states.
void initializeSolver(mfem::Solver *mfem_solver, const smith::FiniteElementState &u)
Initialize mfem solver if near-nullspace is needed.
std::shared_ptr< LinearDifferentiableSolver > buildDifferentiableLinearSolver(LinearSolverOptions linear_opts, const smith::Mesh &mesh)
Create a differentiable linear solver.
std::shared_ptr< NonlinearDifferentiableBlockSolver > buildDifferentiableNonlinearBlockSolver(NonlinearSolverOptions nonlinear_opts, LinearSolverOptions linear_opts, const smith::Mesh &mesh)
Create a differentiable nonlinear block solver.
#define SMITH_MARK_FUNCTION
Definition: profiling.hpp:90
This file contains enumerations and record types for physics solver configuration.
Classes for defining an mfem::Operator with std::functions.
Parameters for an iterative linear solution scheme.
Nonlinear solution scheme parameters.