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)
68 lines
3.4 KiB
C++
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;
|
|
}
|
|
}
|