Files
MeanField_SERiF/src/lib/serif/solver/nonlinear/options.cpp
Emily Boudreaux 9da779417b 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)
2026-11-09 13:27:14 -05:00

68 lines
3.4 KiB
C++

#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;
}
}