8 #include "smith/numerics/block_preconditioner.hpp"
20 #include "smith/smith_config.hpp"
22 #include "smith/numerics/trust_region_solver.hpp"
31 class PreconditionerOnlySolver :
public mfem::IterativeSolver {
33 PreconditionerOnlySolver(MPI_Comm mpi_comm) : mfem::IterativeSolver(mpi_comm) {}
36 void Mult(
const mfem::Vector& x, mfem::Vector& y)
const override
46 void SetOperator(
const mfem::Operator& op)
override
49 prec->SetOperator(op);
59 class SolverWithPreconditioner :
public mfem::Solver {
61 SolverWithPreconditioner(std::unique_ptr<mfem::Solver> linear_solver, std::unique_ptr<mfem::Solver> preconditioner)
62 : linear_solver_(std::move(linear_solver)), preconditioner_(std::move(preconditioner))
64 SLIC_ERROR_IF(!linear_solver_,
"SolverWithPreconditioner requires a non-null linear solver");
67 void SetOperator(
const mfem::Operator& op)
override
71 linear_solver_->SetOperator(op);
74 void Mult(
const mfem::Vector& x, mfem::Vector& y)
const override { linear_solver_->Mult(x, y); }
77 std::unique_ptr<mfem::Solver> linear_solver_;
78 std::unique_ptr<mfem::Solver> preconditioner_;
81 bool preconditionerSupportsBlockOperator(
Preconditioner preconditioner)
83 switch (preconditioner) {
94 bool linearSolverSupportsBlockOperator(
LinearSolver linear_solver)
96 switch (linear_solver) {
100 #ifdef MFEM_USE_STRUMPACK
103 #ifdef SMITH_USE_PETSC
114 bool monolithicizeOperatorIfNeeded(
const LinearSolverOptions& linear_options, mfem::Operator& assembled_gradient,
115 mfem::Operator*& gradient_operator)
117 auto* block_gradient =
dynamic_cast<const mfem::BlockOperator*
>(&assembled_gradient);
118 if (!block_gradient) {
119 gradient_operator = &assembled_gradient;
124 gradient_operator = &assembled_gradient;
130 axom::fmt::format(
"Automatically monolithicizing block Jacobian for linear solver {} with "
140 class NewtonSolver :
public mfem::NewtonSolver,
public ConvergenceManagedNonlinearSolver {
143 mutable mfem::Vector x0;
152 mutable size_t print_level = 0;
155 mutable bool grad_monolithic =
false;
157 std::shared_ptr<EquationSolverConvergenceManager> convergence_manager_ =
nullptr;
162 : nonlinear_options(nonlinear_opts), linear_options(linear_opts)
169 : mfem::NewtonSolver(comm_), nonlinear_options(nonlinear_opts), linear_options(linear_opts)
175 virtual ~NewtonSolver()
177 if (grad_monolithic)
delete grad;
180 void setConvergenceManager(std::shared_ptr<EquationSolverConvergenceManager> convergence_manager)
override
182 convergence_manager_ = std::move(convergence_manager);
186 ConvergenceStatus evaluateConvergence(
const mfem::Vector& x, mfem::Vector& rOut)
const
189 ConvergenceStatus status;
194 if (convergence_manager_) {
195 status = convergence_manager_->evaluate(1.0, rOut);
197 status.block_norms = {Norm(rOut)};
198 status.global_norm = status.block_norms.front();
199 status.global_goal =
std::max(rel_tol * initial_norm, abs_tol);
200 status.global_converged = status.global_norm <= status.global_goal;
201 status.converged = status.global_converged;
203 }
catch (
const std::exception&) {
211 void assembleJacobian(
const mfem::Vector& x)
const
214 if (grad_monolithic) {
217 grad_monolithic =
false;
219 mfem::Operator& assembled_gradient = oper->GetGradient(x);
220 grad_monolithic = monolithicizeOperatorIfNeeded(linear_options, assembled_gradient, grad);
224 void setPreconditioner()
const
227 prec->SetOperator(*grad);
231 void solveLinearSystem(
const mfem::Vector& r_, mfem::Vector& c_)
const
238 void Mult(
const mfem::Vector&, mfem::Vector& x)
const override
240 MFEM_ASSERT(oper != NULL,
"the Operator is not set (use SetOperator).");
241 MFEM_ASSERT(prec != NULL,
"the Solver is not set (use SetSolver).");
243 print_level = print_options.iterations ? 1 : print_level;
244 print_level = print_options.summary ? 2 : print_level;
246 using real_t = mfem::real_t;
248 ConvergenceStatus status = evaluateConvergence(x, r);
249 real_t
norm = status.global_norm;
251 if (
norm == 0.0)
return;
253 if (print_level == 1) {
254 mfem::out <<
"Newton iteration " << std::setw(3) << 0 <<
" : ||r|| = " << std::setw(13) <<
norm <<
"\n";
257 prec->iterative_mode =
false;
261 MFEM_ASSERT(mfem::IsFinite(
norm),
"norm = " <<
norm);
262 if (print_level >= 2) {
263 mfem::out <<
"Newton iteration " << std::setw(3) << it <<
" : ||r|| = " << std::setw(13) <<
norm;
265 mfem::out <<
", ||r||/||r_0|| = " << std::setw(13) << (initial_norm != 0.0 ?
norm / initial_norm :
norm);
270 if ((print_level >= 1) && (
norm !=
norm)) {
271 mfem::out <<
"Initial residual for Newton iteration is undefined/nan.\n";
272 mfem::out <<
"Newton: No convergence!\n";
279 }
else if (it >= max_iter) {
284 real_t norm_nm1 =
norm;
288 solveLinearSystem(r, c);
291 x0.SetSize(x.Size());
295 real_t stepScale = 1.0;
296 add(x0, -stepScale, c, x);
297 status = evaluateConvergence(x, r);
298 norm = status.global_norm;
301 static constexpr real_t reduction = 0.5;
303 const double sufficientDecreaseParam = 0.0;
304 const double cMagnitudeInR = sufficientDecreaseParam != 0.0 ?
std::abs(Dot(c, r)) / norm_nm1 : 0.0;
306 auto is_improved = [=](real_t currentNorm, real_t c_scale) {
307 return currentNorm < norm_nm1 - sufficientDecreaseParam * c_scale * cMagnitudeInR;
313 for (; !is_improved(
norm, stepScale) && ls_iter < max_ls_iters; ++ls_iter, ++ls_iter_sum) {
314 stepScale *= reduction;
315 add(x0, -stepScale, c, x);
316 status = evaluateConvergence(x, r);
317 norm = status.global_norm;
321 if (max_ls_iters > 0 && ls_iter == max_ls_iters && !is_improved(
norm, stepScale)) {
323 add(x0, stepScale, c, x);
324 status = evaluateConvergence(x, r);
325 norm = status.global_norm;
328 for (; !is_improved(
norm, stepScale) && ls_iter < max_ls_iters; ++ls_iter, ++ls_iter_sum) {
329 stepScale *= reduction;
330 add(x0, stepScale, c, x);
331 status = evaluateConvergence(x, r);
332 norm = status.global_norm;
337 if (ls_iter == max_ls_iters && !is_improved(
norm, stepScale)) {
339 stepScale *= reduction;
340 add(x0, -stepScale, c, x);
341 status = evaluateConvergence(x, r);
342 norm = status.global_norm;
347 if (print_level >= 2) {
348 mfem::out <<
"Number of line search steps taken = " << ls_iter_sum << std::endl;
350 if (print_level >= 2 && (ls_iter_sum == 2 * max_ls_iters + 1)) {
351 mfem::out <<
"The maximum number of line search cut back have occurred, the resulting residual may not have "
361 if (print_level == 1) {
362 mfem::out <<
"Newton iteration " << std::setw(3) << final_iter <<
" : ||r|| = " << std::setw(13) <<
norm <<
'\n';
364 if (!converged && print_level >= 1) {
365 mfem::out <<
"Newton: No convergence!\n";
371 struct TrustRegionSettings {
373 double cg_tol = 1e-8;
375 size_t min_cg_iterations = 0;
377 size_t max_cg_iterations = 10000;
379 size_t max_cumulative_iteration = 1;
381 double min_tr_size = 1e-13;
397 struct TrustRegionResults {
399 TrustRegionResults(
int size)
404 H_d_old.SetSize(
size);
408 cauchy_point.SetSize(
size);
409 H_cauchy_point.SetSize(
size);
435 mfem::Vector H_d_old;
443 mfem::Vector cauchy_point;
445 mfem::Vector H_cauchy_point;
447 Status interior_status = Status::Interior;
449 size_t cg_iterations_count = 0;
453 void printTrustRegionInfo(
double realObjective,
double modelObjective,
size_t cgIters,
double trSize,
bool willAccept)
455 mfem::out <<
"real energy = " << std::setw(13) << realObjective <<
", model energy = " << std::setw(13)
456 << modelObjective <<
", cg iter = " << std::setw(7) << cgIters <<
", next tr size = " << std::setw(8)
457 << trSize <<
", accepting = " << willAccept << std::endl;
469 class TrustRegion :
public mfem::NewtonSolver,
public ConvergenceManagedNonlinearSolver {
472 mutable mfem::Vector x_pred;
474 mutable mfem::Vector r_pred;
476 mutable mfem::Vector scratch;
478 mutable std::vector<std::shared_ptr<mfem::Vector>> left_mosts;
480 mutable std::vector<std::shared_ptr<mfem::Vector>> H_left_mosts;
492 mutable size_t print_level = 0;
495 mutable bool grad_monolithic =
false;
497 std::shared_ptr<EquationSolverConvergenceManager> convergence_manager_ =
nullptr;
501 mutable size_t num_hess_vecs = 0;
503 mutable size_t num_preconds = 0;
505 mutable size_t num_residuals = 0;
507 mutable size_t num_subspace_solves = 0;
509 mutable size_t num_jacobian_assembles = 0;
515 : mfem::NewtonSolver(comm_), nonlinear_options(nonlinear_opts), linear_options(linear_opts), tr_precond(tPrec)
523 if (grad_monolithic)
delete grad;
526 void setConvergenceManager(std::shared_ptr<EquationSolverConvergenceManager> convergence_manager)
override
528 convergence_manager_ = std::move(convergence_manager);
532 void projectToBoundaryWithCoefs(mfem::Vector& z,
const mfem::Vector& d,
double delta,
double zz,
double zd,
536 double deltadelta_m_zz = delta * delta - zz;
537 if (deltadelta_m_zz == 0)
return;
538 double tau = (
std::sqrt(deltadelta_m_zz * dd + zd * zd) - zd) / dd;
543 template <
typename HessVecFunc>
544 void solveTheSubspaceProblem([[maybe_unused]] mfem::Vector& z, [[maybe_unused]]
const HessVecFunc& hess_vec_func,
545 [[maybe_unused]]
const std::vector<const mfem::Vector*> ds,
546 [[maybe_unused]]
const std::vector<const mfem::Vector*> Hds,
547 [[maybe_unused]]
const mfem::Vector& g, [[maybe_unused]]
double delta,
548 [[maybe_unused]]
int num_leftmost)
const
550 #ifdef SMITH_USE_SLEPC
552 ++num_subspace_solves;
554 std::vector<const mfem::Vector*> directions;
556 directions.emplace_back(d);
558 for (
auto& left : left_mosts) {
559 directions.emplace_back(left.get());
562 std::vector<const mfem::Vector*> H_directions;
563 for (
auto& Hd : Hds) {
564 H_directions.emplace_back(Hd);
566 for (
auto& H_left : H_left_mosts) {
567 H_directions.emplace_back(H_left.get());
571 std::tie(directions, H_directions) = removeDependentDirections(directions, H_directions);
572 }
catch (
const std::exception& e) {
573 if (print_level >= 2) {
574 mfem::out <<
"remove dependent directions failed with " << e.what() << std::endl;
583 std::vector<std::shared_ptr<mfem::Vector>> leftvecs;
584 std::vector<double> leftvals;
585 double energy_change;
588 std::tie(sol, leftvecs, leftvals, energy_change) =
589 solveSubspaceProblem(directions, H_directions, b, delta, num_leftmost);
590 }
catch (
const std::exception& e) {
591 if (print_level == 1) {
592 mfem::out <<
"subspace solve failed with " << e.what() << std::endl;
598 for (
auto& lv : leftvecs) {
599 left_mosts.emplace_back(std::move(lv));
602 double base_energy = computeEnergy(g, hess_vec_func, z);
603 double subspace_energy = computeEnergy(g, hess_vec_func, sol);
605 if (print_level >= 2) {
606 double leftval = leftvals.size() ? leftvals[0] : 1.0;
607 mfem::out <<
"Energy using subspace solver from: " << base_energy <<
", to: " << subspace_energy <<
" / "
608 << energy_change <<
". Min eig: " << leftval << std::endl;
611 if (subspace_energy < base_energy) {
618 void projectToBoundaryBetweenWithCoefs(mfem::Vector& z,
const mfem::Vector& y,
double trSize,
double zz,
double zy,
621 double dd = yy - 2 * zy + zz;
623 double tau = (
std::sqrt((trSize * trSize - zz) * dd + zd * zd) - zd) / dd;
629 void doglegStep(
const mfem::Vector& cp,
const mfem::Vector& newtonP,
double trSize, mfem::Vector& s)
const
633 double cc = Dot(cp, cp);
634 double nn = Dot(newtonP, newtonP);
635 double tt = trSize * trSize;
640 }
else if (cc > nn) {
641 if (print_level >= 2) {
642 mfem::out <<
"cp outside newton, preconditioner likely inaccurate\n";
645 }
else if (nn > tt) {
647 double cn = Dot(cp, newtonP);
648 projectToBoundaryBetweenWithCoefs(s, newtonP, trSize, cc, cn, nn);
655 template <
typename HessVecFunc>
656 double computeEnergy(
const mfem::Vector& r_local,
const HessVecFunc& H,
const mfem::Vector& z)
const
659 double rz = Dot(r_local, z);
660 mfem::Vector tmp(r_local);
663 return rz + 0.5 * Dot(z, tmp);
667 template <
typename HessVecFunc,
typename PrecondFunc>
668 void solveTrustRegionModelProblem(
const mfem::Vector& r0, mfem::Vector& rCurrent, HessVecFunc hess_vec_func,
669 PrecondFunc precond,
const TrustRegionSettings& settings,
double& trSize,
670 TrustRegionResults& results)
const
674 results.interior_status = TrustRegionResults::Status::Interior;
675 results.cg_iterations_count = 0;
678 auto& cgIter = results.cg_iterations_count;
680 auto& Pr = results.Pr;
681 auto& Hd = results.H_d;
683 const double cg_tol_squared = settings.cg_tol * settings.cg_tol;
685 if (Dot(r0, r0) <= cg_tol_squared && settings.min_cg_iterations == 0) {
686 if (print_level >= 2) {
687 mfem::out <<
"Trust region solution state within tolerance on first iteration."
694 precond(rCurrent, Pr);
702 double rPr = Dot(rCurrent, Pr);
704 double dd = Dot(d, d);
708 for (cgIter = 1; cgIter <= settings.max_cg_iterations; ++cgIter) {
710 if (Dot(d, rCurrent) > 0) {
712 results.interior_status = TrustRegionResults::Status::NonDescentDirection;
715 hess_vec_func(d, Hd);
716 const double curvature = Dot(d, Hd);
717 const double alphaCg = curvature != 0.0 ? rPr / curvature : 0.0;
722 add(z, alphaCg, d, zPred);
723 double zzNp1 = Dot(zPred, zPred);
725 const bool go_to_boundary = curvature <= 0 || zzNp1 >= trSize * trSize;
726 if (go_to_boundary) {
727 projectToBoundaryWithCoefs(z, d, trSize, zz, zd, dd);
728 if (curvature <= 0) {
729 results.interior_status = TrustRegionResults::Status::NegativeCurvature;
731 results.interior_status = TrustRegionResults::Status::OnBoundary;
738 if (results.interior_status == TrustRegionResults::Status::NonDescentDirection) {
739 if (print_level >= 2) {
740 mfem::out <<
"Found a non descent direction\n";
745 add(rCurrent, alphaCg, Hd, rCurrent);
747 precond(rCurrent, Pr);
748 double rPrNp1 = Dot(rCurrent, Pr);
750 if (Dot(rCurrent, rCurrent) <= cg_tol_squared && cgIter >= settings.min_cg_iterations) {
754 double beta = rPrNp1 / rPr;
756 add(-1.0, Pr, beta, d, d);
766 void assembleJacobian(
const mfem::Vector& x)
const
769 ++num_jacobian_assembles;
770 if (grad_monolithic) {
773 grad_monolithic =
false;
775 mfem::Operator& assembled_gradient = oper->GetGradient(x);
776 grad_monolithic = monolithicizeOperatorIfNeeded(linear_options, assembled_gradient, grad);
780 mfem::real_t computeResidual(
const mfem::Vector& x_, mfem::Vector& r_)
const
788 ConvergenceStatus evaluateConvergence(
const mfem::Vector& x_, mfem::Vector& r_)
const
790 ConvergenceStatus status;
794 status.global_norm = computeResidual(x_, r_);
795 if (convergence_manager_) {
796 status = convergence_manager_->evaluate(1.0, r_);
798 status.block_norms = {status.global_norm};
799 status.global_goal =
std::max(rel_tol * initial_norm, abs_tol);
800 status.global_converged = status.global_norm <= status.global_goal;
801 status.converged = status.global_converged;
803 }
catch (
const std::exception&) {
811 void hessVec(
const mfem::Vector& x_, mfem::Vector& v_)
const
819 void precond(
const mfem::Vector& x_, mfem::Vector& v_)
const
823 tr_precond.Mult(x_, v_);
827 void Mult(
const mfem::Vector&, mfem::Vector& X)
const override
829 MFEM_ASSERT(oper != NULL,
"the Operator is not set (use SetOperator).");
830 MFEM_ASSERT(prec != NULL,
"the Solver is not set (use SetSolver).");
832 print_level = print_options.iterations ? 1 : print_level;
833 print_level = print_options.summary ? 2 : print_level;
835 using real_t = mfem::real_t;
840 num_subspace_solves = 0;
841 num_jacobian_assembles = 0;
843 ConvergenceStatus status = evaluateConvergence(X, r);
844 real_t
norm = status.global_norm;
845 real_t norm_goal = status.global_goal;
847 if (
norm == 0.0)
return;
849 if (print_level == 1) {
850 mfem::out <<
"TrustRegion iteration " << std::setw(3) << 0 <<
" : ||r|| = " << std::setw(13) <<
norm <<
"\n";
853 prec->iterative_mode =
false;
854 tr_precond.iterative_mode =
false;
857 x_pred.SetSize(X.Size());
859 r_pred.SetSize(X.Size());
861 scratch.SetSize(X.Size());
864 TrustRegionResults trResults(X.Size());
865 TrustRegionSettings settings;
866 settings.min_cg_iterations =
static_cast<size_t>(nonlinear_options.
min_iterations);
867 settings.max_cg_iterations =
static_cast<size_t>(linear_options.
max_iterations);
868 settings.cg_tol = 0.5 * norm_goal;
875 size_t cumulative_cg_iters_from_last_precond_update = 0;
879 MFEM_ASSERT(mfem::IsFinite(
norm),
"norm = " <<
norm);
880 if (print_level >= 2) {
881 mfem::out <<
"TrustRegion iteration " << std::setw(3) << it <<
" : ||r|| = " << std::setw(13) <<
norm;
883 mfem::out <<
", ||r||/||r_0|| = " << std::setw(13) << (initial_norm != 0.0 ?
norm / initial_norm :
norm);
884 mfem::out <<
", x_incr = " << std::setw(13) << trResults.d.Norml2();
886 mfem::out <<
", norm goal = " << std::setw(13) << norm_goal;
891 if (print_level >= 1 && (
norm !=
norm)) {
892 mfem::out <<
"Initial residual for trust-region iteration is undefined/nan." << std::endl;
893 mfem::out <<
"TrustRegion: No convergence!\n";
900 }
else if (it >= max_iter) {
907 if (it == 0 || (trResults.cg_iterations_count >= settings.max_cg_iterations ||
908 cumulative_cg_iters_from_last_precond_update >= settings.max_cumulative_iteration)) {
909 tr_precond.SetOperator(*grad);
910 cumulative_cg_iters_from_last_precond_update = 0;
913 auto hess_vec_func = [&](
const mfem::Vector& x_, mfem::Vector& v_) { hessVec(x_, v_); };
914 auto precond_func = [&](
const mfem::Vector& x_, mfem::Vector& v_) { precond(x_, v_); };
916 double cauchyPointNormSquared = tr_size * tr_size;
919 hess_vec_func(r, trResults.H_d);
920 const double gKg = Dot(r, trResults.H_d);
922 const double alphaCp = -Dot(r, r) / gKg;
923 add(trResults.cauchy_point, alphaCp, r, trResults.cauchy_point);
924 cauchyPointNormSquared = Dot(trResults.cauchy_point, trResults.cauchy_point);
926 const double alphaTr = -tr_size /
std::sqrt(Dot(r, r));
927 add(trResults.cauchy_point, alphaTr, r, trResults.cauchy_point);
928 if (print_level >= 2) {
929 mfem::out <<
"Negative curvature un-preconditioned cauchy point direction found."
934 if (cauchyPointNormSquared >= tr_size * tr_size) {
935 if (print_level >= 2) {
936 mfem::out <<
"Un-preconditioned gradient cauchy point outside trust region, step size = "
937 <<
std::sqrt(cauchyPointNormSquared) <<
"\n";
939 trResults.cauchy_point *= (tr_size /
std::sqrt(cauchyPointNormSquared));
940 trResults.z = trResults.cauchy_point;
942 trResults.cg_iterations_count = 1;
943 trResults.interior_status = TrustRegionResults::Status::OnBoundary;
945 settings.cg_tol =
std::max(0.5 * norm_goal, 5e-5 *
norm);
946 solveTrustRegionModelProblem(r, scratch, hess_vec_func, precond_func, settings, tr_size, trResults);
948 cumulative_cg_iters_from_last_precond_update += trResults.cg_iterations_count;
950 bool have_computed_Hvs =
false;
952 int lineSearchIter = 0;
956 doglegStep(trResults.cauchy_point, trResults.z, tr_size, trResults.d);
958 bool use_with_option1 =
959 (subspace_option >= 1) && (trResults.interior_status == TrustRegionResults::Status::NonDescentDirection ||
960 trResults.interior_status == TrustRegionResults::Status::NegativeCurvature ||
961 ((Norm(trResults.d) > (1.0 - 1.0e-6) * tr_size) && lineSearchIter > 1));
962 bool use_with_option2 = (subspace_option >= 2) && (Norm(trResults.d) > (1.0 - 1.0e-6) * tr_size);
963 bool use_with_option3 = (subspace_option >= 3);
965 if (use_with_option1 || use_with_option2 || use_with_option3) {
966 if (!have_computed_Hvs) {
967 have_computed_Hvs =
true;
968 hess_vec_func(trResults.z, trResults.H_z);
969 hess_vec_func(trResults.d_old, trResults.H_d_old);
970 hess_vec_func(trResults.cauchy_point, trResults.H_cauchy_point);
973 H_left_mosts.clear();
974 for (
auto& left : left_mosts) {
975 H_left_mosts.emplace_back(std::make_shared<mfem::Vector>(*left));
976 hess_vec_func(*left, *H_left_mosts.back());
979 std::vector<const mfem::Vector*> ds{&trResults.z, &trResults.d_old, &trResults.cauchy_point};
980 std::vector<const mfem::Vector*> H_ds{&trResults.H_z, &trResults.H_d_old, &trResults.H_cauchy_point};
981 solveTheSubspaceProblem(trResults.d, hess_vec_func, ds, H_ds, r, tr_size, num_leftmost);
984 static constexpr
double roundOffTol = 0.0;
986 hess_vec_func(trResults.d, trResults.H_d);
987 double dHd = Dot(trResults.d, trResults.H_d);
988 double modelObjective = Dot(r, trResults.d) + 0.5 * dHd - roundOffTol;
990 add(X, trResults.d, x_pred);
995 auto predicted_status = evaluateConvergence(x_pred, r_pred);
996 normPred = predicted_status.global_norm;
997 double obj1 = 0.5 * (Dot(r, trResults.d) + Dot(r_pred, trResults.d)) - roundOffTol;
998 realObjective = obj1;
999 if (predicted_status.converged) {
1000 trResults.d_old = trResults.d;
1003 status = predicted_status;
1004 norm = status.global_norm;
1005 if (print_level >= 2) {
1006 printTrustRegionInfo(realObjective, modelObjective, trResults.cg_iterations_count, tr_size,
true);
1007 trResults.cg_iterations_count = 0;
1011 }
catch (
const std::exception&) {
1016 double modelImprove = -modelObjective;
1017 double realImprove = -realObjective;
1019 double rho = realImprove / modelImprove;
1020 if (modelObjective > 0) {
1021 if (print_level >= 2) {
1022 mfem::out <<
"Found a positive model objective increase. Debug if you see this.\n";
1024 rho = realImprove / -modelImprove;
1030 if (!(rho >= settings.eta2) ||
1031 rho > settings.eta4) {
1032 tr_size *= settings.t1;
1033 }
else if ((rho > settings.eta3 && rho <= settings.eta4 &&
1034 trResults.interior_status == TrustRegionResults::Status::OnBoundary) ||
1035 (rho > 0.95 && rho < 1.05 &&
1036 trResults.interior_status ==
1037 TrustRegionResults::Status::NegativeCurvature)) {
1039 tr_size *= settings.t2;
1046 bool willAccept = rho >= settings.eta1 && rho <= settings.eta4;
1048 if (print_level >= 2) {
1049 printTrustRegionInfo(realObjective, modelObjective, trResults.cg_iterations_count, tr_size, willAccept);
1050 trResults.cg_iterations_count =
1055 trResults.d_old = trResults.d;
1058 status = convergence_manager_ ? convergence_manager_->evaluate(1.0, r_pred) : status;
1068 if (print_level == 1) {
1069 mfem::out <<
"TrustRegion iteration " << std::setw(3) << final_iter <<
" : ||r|| = " << std::setw(13) <<
norm
1072 if (!converged && print_level >= 1) {
1073 mfem::out <<
"TrustRegion: No convergence!\n";
1076 if (
false && print_level >= 2) {
1077 mfem::out <<
"num hess vecs = " << num_hess_vecs <<
"\n";
1078 mfem::out <<
"num preconds = " << num_preconds <<
"\n";
1079 mfem::out <<
"num residuals = " << num_residuals <<
"\n";
1080 mfem::out <<
"num subspace solves = " << num_subspace_solves <<
"\n";
1081 mfem::out <<
"num jacobian_assembles = " << num_jacobian_assembles <<
"\n";
1091 lin_solver_ = std::move(lin_solver);
1094 convergence_manager_ = std::make_shared<EquationSolverConvergenceManager>(comm, nonlinear_opts.
absolute_tol,
1096 attachConvergenceManager();
1100 std::unique_ptr<mfem::Solver> linear_solver,
1101 std::unique_ptr<mfem::Solver> preconditioner)
1103 SLIC_ERROR_ROOT_IF(!nonlinear_solver,
"Nonlinear solvers must be given to construct an EquationSolver");
1104 SLIC_ERROR_ROOT_IF(!linear_solver,
"Linear solvers must be given to construct an EquationSolver");
1106 nonlin_solver_ = std::move(nonlinear_solver);
1107 lin_solver_ = std::move(linear_solver);
1111 void EquationSolver::attachConvergenceManager()
const
1113 if (!convergence_manager_ || !nonlin_solver_) {
1117 if (
auto* managed_solver =
dynamic_cast<ConvergenceManagedNonlinearSolver*
>(nonlin_solver_.get())) {
1118 managed_solver->setConvergenceManager(convergence_manager_);
1122 void EquationSolver::initializeConvergenceManager(
double abs_tol,
double rel_tol, MPI_Comm comm)
const
1124 if (!convergence_manager_) {
1125 convergence_manager_ = std::make_shared<EquationSolverConvergenceManager>(comm, abs_tol, rel_tol);
1127 convergence_manager_->setTolerances(abs_tol, rel_tol);
1129 attachConvergenceManager();
1134 attachConvergenceManager();
1135 nonlin_solver_->SetOperator(op);
1138 if (!nonlin_solver_set_solver_called_) {
1140 nonlin_solver_set_solver_called_ =
true;
1146 initializeConvergenceManager(abs_tol, rel_tol, comm);
1151 if (convergence_manager_) {
1152 convergence_manager_->reset();
1159 mfem::Vector
zero(x);
1163 nonlin_solver_->Mult(
zero, x);
1168 SLIC_ERROR_ROOT_IF(!superlu_mat_,
"Operator must be set prior to solving with SuperLU");
1171 superlu_solver_.Mult(input, output);
1191 int row_blocks = block_operator.NumRowBlocks();
1192 int col_blocks = block_operator.NumColBlocks();
1194 SLIC_ERROR_ROOT_IF(row_blocks != col_blocks,
"Attempted to use a direct solver on a non-square block system.");
1196 mfem::Array2D<const mfem::HypreParMatrix*> hypre_blocks(row_blocks, col_blocks);
1198 for (
int i = 0; i < row_blocks; ++i) {
1199 for (
int j = 0; j < col_blocks; ++j) {
1201 if (!block_operator.IsZeroBlock(i, j)) {
1202 auto* hypre_block =
dynamic_cast<const mfem::HypreParMatrix*
>(&block_operator.GetBlock(i, j));
1203 SLIC_ERROR_ROOT_IF(!hypre_block,
1204 "Trying to use SuperLU on a block operator that does not contain HypreParMatrix blocks.");
1206 hypre_blocks(i, j) = hypre_block;
1208 hypre_blocks(i, j) =
nullptr;
1215 return std::unique_ptr<mfem::HypreParMatrix>(mfem::HypreParMatrixFromBlocks(hypre_blocks));
1221 auto* block_operator =
dynamic_cast<const mfem::BlockOperator*
>(&op);
1224 if (block_operator) {
1227 superlu_mat_ = std::make_unique<mfem::SuperLURowLocMatrix>(*monolithic_mat_);
1230 auto* matrix =
dynamic_cast<const mfem::HypreParMatrix*
>(&op);
1232 SLIC_ERROR_ROOT_IF(!matrix,
"Matrix must be an assembled HypreParMatrix for use with SuperLU");
1234 superlu_mat_ = std::make_unique<mfem::SuperLURowLocMatrix>(*matrix);
1236 height = op.Height();
1238 superlu_solver_.SetOperator(*superlu_mat_);
1241 #ifdef MFEM_USE_STRUMPACK
1243 void StrumpackSolver::Mult(
const mfem::Vector& input, mfem::Vector& output)
const
1245 SLIC_ERROR_ROOT_IF(!strumpack_mat_,
"Operator must be set prior to solving with Strumpack");
1248 strumpack_solver_.Mult(input, output);
1251 void StrumpackSolver::SetOperator(
const mfem::Operator& op)
1254 auto* block_operator =
dynamic_cast<const mfem::BlockOperator*
>(&op);
1257 if (block_operator) {
1260 strumpack_mat_ = std::make_unique<mfem::STRUMPACKRowLocMatrix>(*monolithic_mat_);
1263 auto* matrix =
dynamic_cast<const mfem::HypreParMatrix*
>(&op);
1265 SLIC_ERROR_ROOT_IF(!matrix,
"Matrix must be an assembled HypreParMatrix for use with Strumpack");
1267 strumpack_mat_ = std::make_unique<mfem::STRUMPACKRowLocMatrix>(*matrix);
1269 height = op.Height();
1271 strumpack_solver_.SetOperator(*strumpack_mat_);
1280 std::unique_ptr<mfem::NewtonSolver> nonlinear_solver;
1284 SLIC_ERROR_ROOT_IF(nonlinear_opts.
min_iterations != 0,
"Newton's method does not support nonzero min_iterations");
1285 nonlinear_solver = std::make_unique<NewtonSolver>(comm, nonlinear_opts, linear_opts);
1288 SLIC_ERROR_ROOT_IF(nonlinear_opts.
min_iterations != 0,
"LBFGS does not support nonzero min_iterations");
1289 nonlinear_solver = std::make_unique<mfem::LBFGSSolver>(comm);
1291 nonlinear_solver = std::make_unique<NewtonSolver>(comm, nonlinear_opts, linear_opts);
1293 nonlinear_solver = std::make_unique<TrustRegion>(comm, nonlinear_opts, linear_opts, prec);
1294 #ifdef SMITH_USE_PETSC
1296 nonlinear_solver = std::make_unique<mfem_ext::PetscNewtonSolver>(comm, nonlinear_opts);
1298 nonlinear_solver = std::make_unique<mfem_ext::PetscNewtonSolver>(comm, nonlinear_opts);
1300 nonlinear_solver = std::make_unique<mfem_ext::PetscNewtonSolver>(comm, nonlinear_opts);
1302 nonlinear_solver = std::make_unique<mfem_ext::PetscNewtonSolver>(comm, nonlinear_opts);
1307 #ifdef SMITH_USE_SUNDIALS
1309 SLIC_ERROR_ROOT_IF(nonlinear_opts.
min_iterations != 0,
"kinsol solvers do not support min_iterations");
1311 int kinsol_strat = KIN_NONE;
1315 kinsol_strat = KIN_NONE;
1318 kinsol_strat = KIN_LINESEARCH;
1321 kinsol_strat = KIN_PICARD;
1324 kinsol_strat = KIN_NONE;
1325 SLIC_ERROR_ROOT(
"Unknown KINSOL nonlinear solver type given.");
1327 auto kinsol_solver = std::make_unique<mfem::KINSolver>(comm, kinsol_strat,
true);
1328 nonlinear_solver = std::move(kinsol_solver);
1330 SLIC_ERROR_ROOT(
"KINSOL was not enabled when MFEM was built");
1334 nonlinear_solver->SetRelTol(nonlinear_opts.
relative_tol);
1335 nonlinear_solver->SetAbsTol(nonlinear_opts.
absolute_tol);
1337 nonlinear_solver->SetPrintLevel(nonlinear_opts.
print_level);
1342 nonlinear_solver->iterative_mode =
true;
1344 return nonlinear_solver;
1353 auto lin_solver = std::make_unique<SuperLUSolver>(linear_opts.
print_level, comm);
1354 return {std::move(lin_solver), std::move(preconditioner)};
1357 #ifdef MFEM_USE_STRUMPACK
1360 auto lin_solver = std::make_unique<StrumpackSolver>(linear_opts.
print_level, comm);
1361 return {std::move(lin_solver), std::move(preconditioner)};
1366 std::unique_ptr<mfem::IterativeSolver> iter_lin_solver;
1370 iter_lin_solver = std::make_unique<mfem::CGSolver>(comm);
1373 iter_lin_solver = std::make_unique<mfem::GMRESSolver>(comm);
1375 #ifdef SMITH_USE_PETSC
1377 iter_lin_solver = std::make_unique<smith::mfem_ext::PetscKSPSolver>(comm, KSPCG, std::string());
1380 iter_lin_solver = std::make_unique<smith::mfem_ext::PetscKSPSolver>(comm, KSPGMRES, std::string());
1385 SLIC_ERROR_ROOT(
"PETSc linear solver requested for non-PETSc build.");
1390 iter_lin_solver = std::make_unique<PreconditionerOnlySolver>(comm);
1393 SLIC_ERROR_ROOT(
"Linear solver type not recognized.");
1400 iter_lin_solver->SetPrintLevel(linear_opts.
print_level);
1402 if (preconditioner) {
1403 iter_lin_solver->SetPreconditioner(*preconditioner);
1406 return {std::move(iter_lin_solver), std::move(preconditioner)};
1411 return !linearSolverSupportsBlockOperator(linear_opts.
linear_solver) ||
1412 !preconditionerSupportsBlockOperator(linear_opts.
preconditioner);
1415 #ifdef MFEM_USE_AMGX
1416 std::unique_ptr<mfem::AmgXSolver> buildAMGX(
const AMGXOptions& options,
const MPI_Comm comm)
1418 auto amgx = std::make_unique<mfem::AmgXSolver>();
1419 conduit::Node options_node;
1420 options_node[
"config_version"] = 2;
1421 auto& solver_options = options_node[
"solver"];
1422 solver_options[
"solver"] =
"AMG";
1423 solver_options[
"presweeps"] = 1;
1424 solver_options[
"postsweeps"] = 2;
1425 solver_options[
"interpolator"] =
"D2";
1426 solver_options[
"max_iters"] = 2;
1427 solver_options[
"convergence"] =
"ABSOLUTE";
1428 solver_options[
"cycle"] =
"V";
1430 if (options.verbose) {
1431 options_node[
"solver/obtain_timings"] = 1;
1432 options_node[
"solver/monitor_residual"] = 1;
1433 options_node[
"solver/print_solve_stats"] = 1;
1440 static const auto solver_names = []() {
1441 std::unordered_map<AMGXSolver, std::string> names;
1459 options_node[
"solver/solver"] = solver_names.at(options.solver);
1460 options_node[
"solver/smoother"] = solver_names.at(options.smoother);
1463 amgx->ReadParameters(options_node.to_json(), mfem::AmgXSolver::INTERNAL);
1464 amgx->InitExclusiveGPU(comm);
1472 std::unique_ptr<mfem::Solver> preconditioner_solver;
1478 auto amg_preconditioner = std::make_unique<mfem::HypreBoomerAMG>();
1479 amg_preconditioner->SetPrintLevel(preconditioner_print_level);
1480 preconditioner_solver = std::move(amg_preconditioner);
1482 auto jac_preconditioner = std::make_unique<mfem::HypreSmoother>();
1483 jac_preconditioner->SetType(mfem::HypreSmoother::Type::Jacobi);
1484 preconditioner_solver = std::move(jac_preconditioner);
1486 auto jacl1_preconditioner = std::make_unique<mfem::HypreSmoother>();
1487 jacl1_preconditioner->SetType(mfem::HypreSmoother::Type::l1Jacobi);
1488 preconditioner_solver = std::move(jacl1_preconditioner);
1490 auto gs_preconditioner = std::make_unique<mfem::HypreSmoother>();
1491 gs_preconditioner->SetType(mfem::HypreSmoother::Type::GS);
1492 preconditioner_solver = std::move(gs_preconditioner);
1494 auto ilu_preconditioner = std::make_unique<mfem::HypreILU>();
1495 ilu_preconditioner->SetLevelOfFill(1);
1496 ilu_preconditioner->SetPrintLevel(preconditioner_print_level);
1497 preconditioner_solver = std::move(ilu_preconditioner);
1499 #ifdef MFEM_USE_AMGX
1500 preconditioner_solver = buildAMGX(linear_opts.
amgx_options, comm);
1502 SLIC_ERROR_ROOT(
"AMGX requested in non-GPU build");
1505 #ifdef SMITH_USE_PETSC
1506 preconditioner_solver = mfem_ext::buildPetscPreconditioner(linear_opts.
petsc_preconditioner, comm);
1508 SLIC_ERROR_ROOT(
"PETSc preconditioner requested in non-PETSc build");
1511 auto amgfcontact_preconditioner = std::make_unique<mfem::AMGFSolver>();
1513 amgfcontact_preconditioner->GetAMG().SetPrintLevel(preconditioner_print_level);
1514 amgfcontact_preconditioner->GetAMG().SetSystemsOptions(amgfcontact_opts.dim_systems_options);
1515 amgfcontact_preconditioner->GetAMG().SetRelaxType(amgfcontact_opts.relax_type);
1516 preconditioner_solver = std::move(amgfcontact_preconditioner);
1519 std::vector<std::unique_ptr<mfem::Solver>> inner_solvers;
1522 inner_solvers.push_back(std::make_unique<SolverWithPreconditioner>(std::move(lin), std::move(prec)));
1526 preconditioner_solver = std::make_unique<BlockDiagonalPreconditioner>(std::move(inner_solvers));
1528 preconditioner_solver =
1529 std::make_unique<BlockTriangularPreconditioner>(std::move(inner_solvers), linear_opts.
block_triangular_type);
1531 preconditioner_solver = std::make_unique<BlockSchurPreconditioner>(
1535 SLIC_ERROR_ROOT_IF(preconditioner !=
Preconditioner::None,
"Unknown preconditioner type requested");
1538 return preconditioner_solver;
1543 auto& linear_container = container.addStruct(
"linear",
"Linear Equation Solver Parameters");
1544 linear_container.required().registerVerifier([](
const axom::inlet::Container& container_to_verify) {
1546 const bool is_iterative = (container_to_verify[
"type"].get<std::string>() ==
"iterative") &&
1547 container_to_verify.contains(
"iterative_options");
1548 const bool is_direct =
1549 (container_to_verify[
"type"].get<std::string>() ==
"direct") && container_to_verify.contains(
"direct_options");
1550 return is_iterative || is_direct;
1554 linear_container.addString(
"type",
"The type of solver parameters to use (iterative|direct)")
1556 .validValues({
"iterative",
"direct"});
1558 auto& iterative_container = linear_container.addStruct(
"iterative_options",
"Iterative solver parameters");
1559 iterative_container.addDouble(
"rel_tol",
"Relative tolerance for the linear solve.").defaultValue(1.0e-6);
1560 iterative_container.addDouble(
"abs_tol",
"Absolute tolerance for the linear solve.").defaultValue(1.0e-8);
1561 iterative_container.addInt(
"max_iter",
"Maximum iterations for the linear solve.").defaultValue(5000);
1562 iterative_container.addInt(
"print_level",
"Linear print level.").defaultValue(0);
1563 iterative_container.addString(
"solver_type",
"Solver type (gmres|minres|cg).").defaultValue(
"gmres");
1564 iterative_container.addString(
"prec_type",
"Preconditioner type (JacobiSmoother|L1JacobiSmoother|AMG|ILU|Petsc).")
1565 .defaultValue(
"JacobiSmoother");
1566 iterative_container.addString(
"petsc_prec_type",
"Type of PETSc preconditioner to use.").defaultValue(
"jacobi");
1568 auto& direct_container = linear_container.addStruct(
"direct_options",
"Direct solver parameters");
1569 direct_container.addInt(
"print_level",
"Linear print level.").defaultValue(0);
1572 auto& nonlinear_container = container.addStruct(
"nonlinear",
"Newton Equation Solver Parameters").required(
false);
1573 nonlinear_container.addDouble(
"rel_tol",
"Relative tolerance for the Newton solve.").defaultValue(1.0e-2);
1574 nonlinear_container.addDouble(
"abs_tol",
"Absolute tolerance for the Newton solve.").defaultValue(1.0e-4);
1575 nonlinear_container.addInt(
"max_iter",
"Maximum iterations for the Newton solve.").defaultValue(500);
1576 nonlinear_container.addInt(
"print_level",
"Nonlinear print level.").defaultValue(0);
1577 nonlinear_container.addString(
"solver_type",
"Solver type (Newton|KINFullStep|KINLineSearch)").defaultValue(
"Newton");
1589 std::string type = base[
"type"];
1591 if (type ==
"direct") {
1593 options.
print_level = base[
"direct_options/print_level"];
1597 auto config = base[
"iterative_options"];
1602 std::string solver_type = config[
"solver_type"];
1603 if (solver_type ==
"gmres") {
1605 }
else if (solver_type ==
"cg") {
1608 std::string msg = std::format(
"Unknown Linear solver type given: '{0}'", solver_type);
1609 SLIC_ERROR_ROOT(msg);
1611 const std::string prec_type = config[
"prec_type"];
1612 if (prec_type ==
"JacobiSmoother") {
1614 }
else if (prec_type ==
"L1JacobiSmoother") {
1616 }
else if (prec_type ==
"HypreAMG") {
1618 }
else if (prec_type ==
"ILU") {
1620 #ifdef MFEM_USE_AMGX
1621 }
else if (prec_type ==
"AMGX") {
1624 }
else if (prec_type ==
"GaussSeidel") {
1626 #ifdef SMITH_USE_PETSC
1627 }
else if (prec_type ==
"Petsc") {
1628 const std::string petsc_prec = config[
"petsc_prec_type"];
1632 }
else if (prec_type ==
"AMGFContact") {
1635 std::string msg = std::format(
"Unknown preconditioner type given: '{0}'", prec_type);
1636 SLIC_ERROR_ROOT(msg);
1649 const std::string solver_type = base[
"solver_type"];
1650 if (solver_type ==
"Newton") {
1652 }
else if (solver_type ==
"KINFullStep") {
1654 }
else if (solver_type ==
"KINLineSearch") {
1656 }
else if (solver_type ==
"KINPicard") {
1659 SLIC_ERROR_ROOT(std::format(
"Unknown nonlinear solver type given: '{0}'", solver_type));
1672 std::move(linear_solver), std::move(preconditioner));
This class manages the objects typically required to solve a nonlinear set of equations arising from ...
void resetConvergenceState() const
Reset nonlinear convergence state stored across iterations of a single solve.
mfem::Solver & preconditioner()
void setConvergenceTolerances(double abs_tol, double rel_tol, MPI_Comm comm) const
Update scalar convergence tolerances for the managed nonlinear convergence path.
EquationSolver(std::unique_ptr< mfem::NewtonSolver > nonlinear_solver, std::unique_ptr< mfem::Solver > linear_solver, std::unique_ptr< mfem::Solver > preconditioner=nullptr)
void solve(mfem::Vector &x) const
static void defineInputFileSchema(axom::inlet::Container &container)
mfem::Solver & linearSolver()
void setOperator(const mfem::Operator &op)
void SetOperator(const mfem::Operator &op)
Set the underlying matrix operator to use in the solution algorithm.
void Mult(const mfem::Vector &input, mfem::Vector &output) const
Factor and solve the linear system y = Op^{-1} x using DSuperLU.
This file contains the declaration of an equation solver wrapper.
This file contains the all the necessary functions and macros required for logging as well as a helpe...
Accelerator functionality.
std::unique_ptr< mfem::NewtonSolver > buildNonlinearSolver(NonlinearSolverOptions nonlinear_opts, const LinearSolverOptions &linear_opts, mfem::Solver &prec, MPI_Comm comm)
Build a nonlinear solver using the nonlinear option struct.
bool requiresMonolithicOperator(const LinearSolverOptions &linear_opts)
Return true if the configured linear solve stack requires block operators to be merged.
std::unique_ptr< mfem::HypreParMatrix > buildMonolithicMatrix(const mfem::BlockOperator &block_operator)
Build a monolithic HypreParMatrix from a BlockOperator.
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.
std::string linearName(const LinearSolver &s)
Convert linear solver enums to their string names.
LinearSolver
Linear solution method indicator.
Preconditioner
The type of preconditioner to be used.
SMITH_HOST_DEVICE auto sqrt(dual< gradient_type > x)
implementation of square root for dual numbers
constexpr SMITH_HOST_DEVICE auto norm(const isotropic_tensor< T, m, m > &I)
compute the Frobenius norm (sqrt(tr(dot(transpose(I), I)))) of an isotropic tensor
SMITH_HOST_DEVICE auto max(dual< gradient_type > a, double b)
Implementation of max for dual numbers.
constexpr SMITH_HOST_DEVICE int size(const tensor< T, n... > &)
returns the total number of stored values in a tensor
std::unique_ptr< mfem::Solver > buildPreconditioner(LinearSolverOptions linear_opts, [[maybe_unused]] MPI_Comm comm)
Build a preconditioner from the available options.
SMITH_HOST_DEVICE auto abs(dual< gradient_type > x)
Implementation of absolute value function for dual numbers.
@ PetscNewtonBacktracking
@ PetscNewtonCriticalPoint
@ KINBacktrackingLineSearch
std::string preconditionerName(Preconditioner p)
Convert preconditioner enums to their string names.
Various helper functions and macros for profiling using Caliper.
#define SMITH_MARK_FUNCTION
smith::EquationSolver operator()(const axom::inlet::Container &base)
Returns created object from Inlet container.
smith::LinearSolverOptions operator()(const axom::inlet::Container &base)
Returns created object from Inlet container.
smith::NonlinearSolverOptions operator()(const axom::inlet::Container &base)
Returns created object from Inlet container.
Parameters for an iterative linear solution scheme.
int preconditioner_print_level
Debugging print level for the preconditioner.
int max_iterations
Maximum number of iterations.
double relative_tol
Relative tolerance.
int print_level
Debugging print level for the linear solver.
AMGFContactOptions amgfcontact_options
AMGFContact Options, used for Preconditioner::AMGFContact.
Preconditioner preconditioner
PreconditionerOptions selection.
SchurApproxType schur_approx_type
Schur approximation type.
PetscPCType petsc_preconditioner
PETSc preconditioner type.
std::vector< LinearSolverOptions > sub_block_linear_solver_options
Subblock linear solver options for block preconditioners.
BlockSchurType block_schur_type
Block Schur preconditioner factorization type.
double absolute_tol
Absolute tolerance.
AMGXOptions amgx_options
AMGX Options, used for Preconditioner::AMGX.
BlockTriangularType block_triangular_type
Block Triangular Preconditioner factorization type.
LinearSolver linear_solver
Linear solver selection.
Nonlinear solution scheme parameters.
int min_iterations
Minimum number of iterations.
int max_iterations
Maximum number of iterations.
double trust_region_scaling
Scaling for the initial trust region size.
int max_line_search_iterations
Maximum line search cutbacks.
double relative_tol
Relative tolerance.
int print_level
Debug print level.
int num_leftmost
Number of extra leftmost eigenvector to be stored between solves.
double absolute_tol
Absolute tolerance.
NonlinearSolver nonlin_solver
Nonlinear solver selection.
SubSpaceOptions subspace_option
Option for how when the subspace solver should be utilized within trust-region solver.
A sentinel struct for eliding no-op tensor operations.