feat(solver/nonlinear): Implements a damped newton line-search with backtracking
This uses a linear solver from solver/linear which then calls some linear solver from the backend. Note that MFEM's build in newton solver is very simplistic and not sufficient for our needs; therefore we implement a more robust one here (and this is why newton is in the frontend and not the backend)
This commit is contained in:
67
src/lib/serif/solver/nonlinear/options.cpp
Normal file
67
src/lib/serif/solver/nonlinear/options.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "serif/solver/nonlinear/options.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::solver {
|
||||
void Newton::validate() const {
|
||||
using namespace utils::errors;
|
||||
serif_ensure<SERiFErrorCode::INVALID_ARGUMENT>(std::isfinite(relative_tolerance) && relative_tolerance >= 0.0 &&
|
||||
std::isfinite(absolute_tolerance) && absolute_tolerance >= 0.0 && maximum_iterations >= 0,
|
||||
"Newton tolerances must be finite and nonnegative, and its iteration budget must be nonnegative.");
|
||||
linear_solve.validate();
|
||||
}
|
||||
|
||||
double Newton::convergence_threshold(const double initial_norm) const {
|
||||
using namespace utils::errors;
|
||||
validate();
|
||||
const double relative = relative_tolerance * initial_norm;
|
||||
serif_ensure<SERiFErrorCode::INVALID_ARGUMENT>(std::isfinite(initial_norm) && initial_norm >= 0.0 && std::isfinite(relative),
|
||||
"Newton requires a finite initial norm and representable relative convergence threshold.");
|
||||
return std::max(absolute_tolerance, relative);
|
||||
}
|
||||
|
||||
void Backtracking::validate() const {
|
||||
using namespace utils::errors;
|
||||
serif_ensure<SERiFErrorCode::INVALID_ARGUMENT>(std::isfinite(initial_step) && initial_step > 0.0 && initial_step <= 1.0 &&
|
||||
std::isfinite(contraction) && contraction > 0.0 && contraction < 1.0 &&
|
||||
std::isfinite(fraction_to_boundary) && fraction_to_boundary > 0.0 && fraction_to_boundary < 1.0 &&
|
||||
std::isfinite(sufficient_decrease) && sufficient_decrease > 0.0 && sufficient_decrease < 1.0 &&
|
||||
std::isfinite(minimum_step) && minimum_step > 0.0 && minimum_step <= initial_step && maximum_trials > 0,
|
||||
"Backtracking needs 0 < minimum step <= initial step <= 1, contraction, boundary safety and sufficient decrease in (0,1), and a positive trial budget.");
|
||||
}
|
||||
|
||||
MetricEvaluation NormalizedResidualMetric::operator()(std::span<const double> residual) const {
|
||||
double norm = 0.0;
|
||||
for (const double value : residual) {
|
||||
norm = std::hypot(norm, value);
|
||||
}
|
||||
return {.residual_norm = norm, .merit = 0.5 * norm * norm};
|
||||
}
|
||||
|
||||
double next_backtracking_step(const double rejected_step, const double accepted_determinant,
|
||||
const double rejected_determinant, const bool inverted, const Backtracking& options) noexcept {
|
||||
const double contracted = rejected_step * options.contraction;
|
||||
if (!inverted || !std::isfinite(accepted_determinant) || accepted_determinant <= 0.0 ||
|
||||
!std::isfinite(rejected_determinant) || rejected_determinant > 0.0) {
|
||||
return contracted;
|
||||
}
|
||||
const double change = accepted_determinant - rejected_determinant;
|
||||
if (!std::isfinite(change) || change <= 0.0) {
|
||||
return contracted;
|
||||
}
|
||||
const double safe_step = options.fraction_to_boundary * rejected_step * accepted_determinant / change;
|
||||
if (!std::isfinite(safe_step) || safe_step <= 0.0 || contracted <= safe_step) {
|
||||
return contracted;
|
||||
}
|
||||
const double rung = std::ceil(std::log(safe_step / rejected_step) / std::log(options.contraction));
|
||||
double skipped = rejected_step * std::pow(options.contraction, rung);
|
||||
if (!std::isfinite(skipped) || skipped <= 0.0 || skipped >= rejected_step) {
|
||||
return contracted;
|
||||
}
|
||||
if (skipped > safe_step) {
|
||||
skipped *= options.contraction;
|
||||
}
|
||||
return skipped;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user