use log::info;
use nalgebra::{DMatrix, DVector};
use strum_macros::Display;
#[derive(Debug, Clone, PartialEq, Display, Copy)]
pub enum ScalingMethod {
Levenberg,
Marquardt,
More,
}
pub struct TrustRegionScaling;
impl TrustRegionScaling {
pub fn init_scaling(jacobian: &DMatrix<f64>, method: &ScalingMethod) -> DVector<f64> {
match method {
ScalingMethod::Levenberg => Self::init_levenberg_scaling(jacobian),
ScalingMethod::Marquardt => Self::init_marquardt_scaling(jacobian),
ScalingMethod::More => Self::init_more_scaling(jacobian),
}
}
pub fn update_scaling(
jacobian: &DMatrix<f64>,
method: &ScalingMethod,
current_diag: &mut DVector<f64>,
) {
match method {
ScalingMethod::Levenberg => {
}
ScalingMethod::Marquardt => {
Self::update_marquardt_scaling(jacobian, current_diag);
}
ScalingMethod::More => {
Self::update_more_scaling(jacobian, current_diag);
}
}
}
fn init_levenberg_scaling(jacobian: &DMatrix<f64>) -> DVector<f64> {
let n_params = jacobian.ncols();
DVector::from_element(n_params, 1.0)
}
fn init_marquardt_scaling(jacobian: &DMatrix<f64>) -> DVector<f64> {
let n_params = jacobian.ncols();
let mut diag = DVector::zeros(n_params);
Self::update_marquardt_scaling(jacobian, &mut diag);
info!("\n Diagonal scaling vector created!: {}", diag);
diag
}
fn update_marquardt_scaling(jacobian: &DMatrix<f64>, diag: &mut DVector<f64>) {
let n_params = jacobian.ncols();
for j in 0..n_params {
let column = jacobian.column(j);
let norm = column.norm();
let scaling_value = if norm < 1e-12 { 1.0 } else { norm };
diag[j] = scaling_value;
}
}
fn init_more_scaling(jacobian: &DMatrix<f64>) -> DVector<f64> {
Self::init_marquardt_scaling(jacobian)
}
fn update_more_scaling(jacobian: &DMatrix<f64>, diag: &mut DVector<f64>) {
let n_params = jacobian.ncols();
for j in 0..n_params {
let column = jacobian.column(j);
let norm = column.norm();
let scaling_value = if norm < 1e-12 { 1.0 } else { norm };
diag[j] = scaling_value.max(diag[j]);
}
}
pub fn create_scaled_regularization(
mu: f64,
diag: &DVector<f64>,
method: &ScalingMethod,
) -> DMatrix<f64> {
let n = diag.len();
let mut regularization = DMatrix::zeros(n, n);
match method {
ScalingMethod::Levenberg => {
for i in 0..n {
regularization[(i, i)] = mu;
}
}
ScalingMethod::Marquardt | ScalingMethod::More => {
for i in 0..n {
regularization[(i, i)] = mu * diag[i] * diag[i];
}
}
}
regularization
}
}
#[derive(Debug, Clone, PartialEq, Display, Copy)]
pub enum ReductionRatio {
Nielsen, More, }
pub struct ReductionRatioSolver {}
impl ReductionRatioSolver {
pub fn solve_reduction_ratio(
Jy: &DMatrix<f64>,
Fy: &DVector<f64>,
Fy_new: &DVector<f64>,
scaling: &DVector<f64>,
p: &DVector<f64>,
mu: f64,
reduction_ratio_method: ReductionRatio,
) -> f64 {
match reduction_ratio_method {
ReductionRatio::Nielsen => {
let rr = Self::nielsen_reduction_ratio(Jy, Fy, Fy_new, p);
rr
}
ReductionRatio::More => {
let rr = Self::gsl_reduction_ratio(Jy, Fy, Fy_new, p, scaling, mu);
rr
}
}
}
fn nielsen_reduction_ratio(
Jy: &DMatrix<f64>,
Fy: &DVector<f64>,
Fy_new: &DVector<f64>,
p: &DVector<f64>,
) -> f64 {
let actual_reduction = Fy.norm_squared() - Fy_new.norm_squared();
let predicted_residual = Fy + Jy * p;
let predicted_reduction = Fy.norm_squared() - predicted_residual.norm_squared();
let rho = if predicted_reduction.abs() > 1e-12 {
actual_reduction / predicted_reduction
} else {
0.0
};
info!(
"rho = {}, actual_reduction = {}, predicted_reduction = {}",
rho, actual_reduction, predicted_reduction
);
rho
}
fn gsl_reduction_ratio(
Jy: &DMatrix<f64>,
Fy: &DVector<f64>,
Fy_new: &DVector<f64>,
p: &DVector<f64>,
scaling: &DVector<f64>,
mu: f64,
) -> f64 {
let norm_f = Fy.norm_squared();
let norm_f_trial = Fy_new.norm_squared();
if norm_f_trial >= norm_f {
info!("Cost increased, rejecting step");
return -1.0; }
let actual_reduction = 1.0 - (norm_f_trial / norm_f).powf(2.0);
let predicted_reduction = Self::predicted_reduction(Fy, Jy, p, scaling, mu);
if predicted_reduction > 0.0 {
let rho = actual_reduction / predicted_reduction;
info!(
"rho = {}, actual_reduction = {}, predicted_reduction = {}",
rho, actual_reduction, predicted_reduction
);
return rho;
} else {
info!(
"rho = {}, actual_reduction = {}, predicted_reduction = {}",
-1.0, actual_reduction, predicted_reduction
);
return -1.0; }
}
fn predicted_reduction(
Fy: &DVector<f64>,
Jy: &DMatrix<f64>,
p: &DVector<f64>,
scaling: &DVector<f64>,
mu: f64,
) -> f64 {
let norm_f = Fy.norm();
let norm_Jp = (Jy * p).norm();
let norm_Dp = (scaling.component_mul(p)).norm();
let u = norm_Jp / norm_f;
let v = norm_Dp / norm_f;
let predicted_reduction = u.powf(2.0) + 2.0 * mu * v.powf(2.0);
predicted_reduction
}
}
#[derive(Debug, Clone, PartialEq, Display)]
pub enum ConvergenceCriteria {
GSL,
SimpleScaled,
}
pub struct ConvergenceCriteriaSolver {}
impl ConvergenceCriteriaSolver {
pub fn test_convergence(
x: &DVector<f64>, dx: &DVector<f64>, g: &DVector<f64>, f: &DVector<f64>, diag: &DVector<f64>, xtol: f64, gtol: Option<f64>, ftol: Option<f64>, convergence_criteria: ConvergenceCriteria,
) -> (bool, i32) {
match convergence_criteria {
ConvergenceCriteria::GSL => test_convergence_gsl(x, dx, g, f, xtol, gtol, ftol),
ConvergenceCriteria::SimpleScaled => test_convergence_simple_scaled(dx, diag, xtol),
}
}
}
fn test_convergence_simple_scaled(
dx: &DVector<f64>,
diag: &DVector<f64>,
xtol: f64,
) -> (bool, i32) {
let scaled_norm = scaled_norm_common(diag, dx);
let converged = scaled_norm < xtol;
let info = if converged { 1 } else { 0 };
(converged, info)
}
pub fn scaled_norm_common(diag: &DVector<f64>, vector: &DVector<f64>) -> f64 {
let mut sum_squares = 0.0;
for i in 0..vector.len() {
let scaled_component = vector[i] * diag[i];
sum_squares += scaled_component * scaled_component;
}
let scaled_norm = sum_squares.sqrt();
info!("scaled norm ||step*D|| {}", scaled_norm);
scaled_norm
}
pub fn test_convergence_gsl(
x: &DVector<f64>, dx: &DVector<f64>, g: &DVector<f64>, f: &DVector<f64>, xtol: f64, gtol: Option<f64>, ftol: Option<f64>, ) -> (bool, i32) {
let gtol = gtol.unwrap_or(1e-6);
let ftol = ftol.unwrap_or(1e-6);
info!("Checking convergence conditions:");
info!(" xtol = {}, gtol = {}, ftol = {}", xtol, gtol, ftol);
info!(
" ||dx|| = {}, ||g|| = {}, ||f|| = {}",
dx.norm(),
g.norm(),
f.norm()
);
if test_parameter_change(dx, x, xtol) {
info!("CONVERGED: Parameter change test passed");
info!(" ||dx|| components are all < xtol = {}", xtol);
return (true, 1);
}
let gnorm = scaled_infnorm(x, g);
let fnorm = f.norm();
let phi = 0.5 * fnorm * fnorm;
let gradient_threshold = gtol * phi.max(1.0);
info!("Gradient test:");
info!(" gnorm = {}", gnorm);
info!(" phi = 0.5 * ||f||² = {}", phi);
info!(" threshold = gtol * max(phi, 1) = {}", gradient_threshold);
if gnorm <= gradient_threshold {
info!("CONVERGED: Gradient test passed");
info!(
" Scaled gradient norm {} <= threshold {}",
gnorm, gradient_threshold
);
return (true, 2);
}
let _ftol_unused = ftol;
info!("No convergence detected:");
info!(" Parameter change test: FAILED");
info!(
" Gradient test: gnorm = {} > threshold = {}",
gnorm, gradient_threshold
);
info!(" Function change test: DISABLED (following GSL)");
(false, 0)
}
fn test_parameter_change(dx: &DVector<f64>, x: &DVector<f64>, xtol: f64) -> bool {
let n = dx.len();
let xtol_squared = xtol * xtol;
info!("\n Parameter change test (component-wise):");
for i in 0..n {
let dx_i = dx[i];
let x_i = x[i];
if dx_i.abs() > xtol {
info!(
" Component {}: |dx| = {} > xtol = {} (FAIL absolute)",
i,
dx_i.abs(),
xtol
);
return false;
}
if dx_i.abs() > xtol_squared * x_i.abs() {
info!(
" Component {}: |dx| = {} > xtol² * |x| = {} (FAIL relative)",
i,
dx_i.abs(),
xtol_squared * x_i.abs()
);
return false;
}
info!(" Component {}: |dx| = {} (PASS both tests)", i, dx_i.abs());
}
info!("All components passed parameter change test");
true
}
fn scaled_infnorm(x: &DVector<f64>, g: &DVector<f64>) -> f64 {
let n = x.len();
let mut max_scaled_grad: f64 = 0.0;
info!("\n Computing scaled gradient norm:");
for i in 0..n {
let x_i = x[i];
let g_i = g[i];
let scale_factor = x_i.abs().max(1.0);
let scaled_grad_i: f64 = g_i.abs() * scale_factor;
max_scaled_grad = max_scaled_grad.max(scaled_grad_i);
info!(
" Component {}: |g| = {}, scale = {}, scaled = {}",
i,
g_i.abs(),
scale_factor,
scaled_grad_i
);
}
info!("Maximum scaled gradient component: {}", max_scaled_grad);
max_scaled_grad
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ConvergenceInfo {
NoConvergence = 0,
ParameterChange = 1,
GradientSmall = 2,
FunctionChange = 3, }
impl From<i32> for ConvergenceInfo {
fn from(code: i32) -> Self {
match code {
1 => ConvergenceInfo::ParameterChange,
2 => ConvergenceInfo::GradientSmall,
3 => ConvergenceInfo::FunctionChange,
_ => ConvergenceInfo::NoConvergence,
}
}
}
impl std::fmt::Display for ConvergenceInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConvergenceInfo::NoConvergence => write!(f, "No convergence"),
ConvergenceInfo::ParameterChange => write!(f, "Converged: parameter change small"),
ConvergenceInfo::GradientSmall => write!(f, "Converged: gradient small"),
ConvergenceInfo::FunctionChange => write!(f, "Converged: function change small"),
}
}
}
#[allow(dead_code)]
fn compute_scaled_gradient_norm(
jacobian: &DMatrix<f64>,
residuals: &DVector<f64>,
residuals_norm: f64,
) -> f64 {
let mut max_scaled_grad: f64 = 0.0;
for j in 0..jacobian.ncols() {
let col = jacobian.column(j);
let col_norm = col.norm();
if col_norm > 0.0 && residuals_norm > 0.0 {
let grad_component = col.dot(residuals).abs();
let scaled_grad: f64 = grad_component / (col_norm * residuals_norm);
max_scaled_grad = max_scaled_grad.max(scaled_grad);
}
}
max_scaled_grad
}
pub fn compute_scaled_gradient_norm2(
jacobian: &DMatrix<f64>, residuals: &DVector<f64>, ) -> Option<f64> {
let residual_norm = residuals.norm();
if residual_norm == 0.0 {
return Some(0.0);
}
let mut max_scaled_gradient: f64 = 0.0;
let n = jacobian.ncols();
for j in 0..n {
let jacobian_column = jacobian.column(j);
let mut column_norm = jacobian_column.norm();
if column_norm.abs() < 1e-40 {
column_norm = 1.0;
}
let gradient_component = jacobian_column.dot(residuals);
let scaled_gradient = gradient_component.abs() / (column_norm * residual_norm);
if scaled_gradient.is_nan() {
return None;
}
max_scaled_gradient = max_scaled_gradient.max(scaled_gradient);
}
Some(max_scaled_gradient)
}