use super::NR::{NR, solve_linear_system};
use crate::numerical::BVP_Damp::BVP_utils::elapsed_time;
use crate::numerical::Nonlinear_systems::dogleg::{DoglegSolver, Powell_dogleg_method};
use log::{error, info};
use nalgebra::{DMatrix, DVector};
use crate::numerical::Nonlinear_systems::LM_utils::{
ConvergenceCriteria, ConvergenceCriteriaSolver, ReductionRatioSolver, ScalingMethod,
TrustRegionScaling, TrustRegionSubproblem, scaled_norm_common,
};
use std::time::Instant;
const ONE_THIRD: f64 = 1.0 / 3.0;
impl NR {
pub fn step_trust_region_Nielsen_simple(&mut self) -> (i32, Option<DVector<f64>>) {
let now = Instant::now();
let bounds_vec = self.bounds_vec.clone();
let parameters = self.parameters.clone().unwrap();
let tau = parameters.get("tau").unwrap();
let method = self.linear_sys_method.clone().unwrap();
let mut parameters_mut = self.parameters.clone().unwrap();
let y = self.y.clone();
let Fy = self.evaluate_function(y.clone());
let Jy = self.evaluate_jacobian(y.clone());
let B = Jy.clone().transpose() * Jy.clone();
let mu0 = tau * (B.diagonal()).max();
let mut mu = if self.i == 0 {
mu0
} else {
*parameters_mut.get("mu").unwrap()
};
let mut nu = *parameters_mut.get("nu").unwrap();
info!("mu = {}, nu = {}", mu, nu);
let g = -Jy.transpose() * Fy.clone();
let A = B.clone() + mu * DMatrix::identity(B.nrows(), B.ncols());
let norm = Fy.norm_squared();
info!("norm = {}", norm);
if norm < self.tolerance {
info!("Solution found!");
return (1, Some(y));
}
let p = solve_linear_system(method, &A, &g).expect("Failed to solve linear system");
let mut y_new = y.clone() + &p;
y_new = self.clip(&y_new, &bounds_vec);
let Fy_new = self.evaluate_function(y_new.clone());
let Jy_new = self.evaluate_jacobian(y_new.clone());
let actual_reduction = Fy.norm_squared() - Fy_new.norm_squared();
let predicted_reduction = 0.5
* p.clone()
.dot(&(p.clone() * mu - Jy_new.transpose() * Fy_new.clone()));
let rho = if predicted_reduction.abs() > 1e-12 {
actual_reduction / predicted_reduction
} else {
0.0
};
info!("rho is: {}", rho);
let elapsed = now.elapsed();
elapsed_time(elapsed);
if rho > 0.0 {
mu = mu * (ONE_THIRD).max(1.0 - (2.0 * rho - 1.0).powi(3));
nu = 2.0;
info!("rho > 0, mu changed to: {:3}, nu changed to: {:3}", mu, nu);
parameters_mut.insert("mu".to_string(), mu);
parameters_mut.insert("nu".to_string(), nu);
self.parameters = Some(parameters_mut);
let y_result = y_new;
return (0, Some(y_result));
} else {
mu = mu * nu;
nu = nu * 2.0;
info!("rho<0,mu changed to: {:3}, nu changed to: {:3}", mu, nu);
parameters_mut.insert("mu".to_string(), mu);
parameters_mut.insert("nu".to_string(), nu);
self.parameters = Some(parameters_mut);
let y_result = y;
return (0, Some(y_result));
}
}
pub fn step_trust_region_Nielsen(&mut self) -> (i32, Option<DVector<f64>>) {
let now = Instant::now();
if let Err(e) = self.preloop_trust_region_Nielsen() {
error!("Preloop initialization failed: {}", e);
return (-1, None);
}
let scaling_method = self.scaling_method.clone().unwrap();
let bounds_vec = self.bounds_vec.clone();
let method = self.linear_sys_method.clone().unwrap();
let subproblem_method = self.subproblem_method.clone().unwrap(); let reduction_ratio_method = self.reduction_ratio.clone().unwrap(); let update_method = self.update_method.clone().unwrap();
let convergence_criteria = self.convergence_criteria.clone().unwrap();
let mut parameters_mut = self.parameters.clone().unwrap();
let mut mu = *parameters_mut.get("mu").unwrap();
let mut nu = *parameters_mut.get("nu").unwrap();
let mut delta = *parameters_mut.get("delta").unwrap();
let factor_up = *parameters_mut.get("factor_up").unwrap();
let factor_down = *parameters_mut.get("factor_down").unwrap();
let rho_threshold = *parameters_mut.get("rho_threshold").unwrap();
let f_tolerance = parameters_mut.get("f_tolerance");
let g_tolerance = parameters_mut.get("g_tolerance");
let y = self.y.clone();
let Fy = self.evaluate_function(y.clone());
let Jy = self.evaluate_jacobian(y.clone());
let diag = if self.i == 0 {
TrustRegionScaling::extract_scaling_from_vector(&self.scales_vec, Jy.ncols())
} else {
let mut current_diag =
TrustRegionScaling::extract_scaling_from_vector(&self.scales_vec, Jy.ncols());
TrustRegionScaling::update_scaling(&Jy, &scaling_method, &mut current_diag);
TrustRegionScaling::store_scaling_in_vector(
&mut self.scales_vec,
¤t_diag,
self.i,
);
current_diag
};
info!("Scaling diagonal: {:?}", diag.as_slice());
info!("Initial: mu = {}, nu = {}, delta = {}", mu, nu, delta);
let g = -Jy.transpose() * &Fy;
let norm = g.norm_squared();
info!("Function norm = {}", norm);
let max_consecutive_rejections = 15;
let mut consecutive_rejections = 0;
let mut found_acceptable_step = false;
while !found_acceptable_step {
info!("=== Inner iteration {} ===", consecutive_rejections);
info!("Current: mu = {}, nu = {}, delta = {}", mu, nu, delta);
let p = TrustRegionSubproblem::solve(
&Jy,
&Fy,
&g,
&diag,
mu,
delta,
&method,
&scaling_method,
subproblem_method.clone(),
);
info!("Step vector: {:?},\n y = {:?}", &p, &y);
let (converged, flag) = ConvergenceCriteriaSolver::test_convergence(
&y, &p, &g, &Fy, &diag, self.tolerance, g_tolerance.copied(), f_tolerance.copied(), convergence_criteria.clone(),
);
if Fy.norm_squared() < self.tolerance {
info!("Solution found (converged)!");
parameters_mut.insert("mu".to_string(), mu);
parameters_mut.insert("nu".to_string(), nu);
parameters_mut.insert("delta".to_string(), delta);
self.parameters = Some(parameters_mut);
return (1, Some(y));
}
if flag == 2 {
info!("Solution found (small step)!");
parameters_mut.insert("mu".to_string(), mu);
parameters_mut.insert("nu".to_string(), nu);
parameters_mut.insert("delta".to_string(), delta);
self.parameters = Some(parameters_mut);
return (1, Some(y));
}
let mut y_new = &y + &p;
y_new = self.clip(&y_new, &bounds_vec);
let Fy_new = self.evaluate_function(y_new.clone());
let rho = ReductionRatioSolver::solve_reduction_ratio(
&Jy,
&Fy,
&Fy_new,
&diag,
&p,
mu,
reduction_ratio_method.clone(),
);
info!("Step norm = {}, rho = {}", p.norm(), rho);
if rho > 0.75 {
delta *= factor_up;
info!("Excellent step (rho > 0.75): expanding delta to {}", delta);
} else if rho < 0.25 {
delta /= factor_down;
info!("Poor step (rho < 0.25): shrinking delta to {}", delta);
}
if rho > rho_threshold {
found_acceptable_step = true;
mu = mu * (ONE_THIRD).max(1.0 - (2.0 * rho - 1.0).powi(3));
nu = 2.0;
info!("Step ACCEPTED: mu updated to {}, nu reset to {}", mu, nu);
parameters_mut.insert("mu".to_string(), mu);
parameters_mut.insert("nu".to_string(), nu);
parameters_mut.insert("delta".to_string(), delta);
self.parameters = Some(parameters_mut);
let elapsed = now.elapsed();
elapsed_time(elapsed);
return (0, Some(y_new));
} else {
consecutive_rejections += 1;
info!(
"Step REJECTED (rho <= 0): consecutive rejections = {}",
consecutive_rejections
);
mu = mu * nu;
nu = nu * 2.0;
if consecutive_rejections > max_consecutive_rejections {
error!(
"Too many consecutive rejections ({}). No progress possible.",
max_consecutive_rejections
);
parameters_mut.insert("mu".to_string(), mu);
parameters_mut.insert("nu".to_string(), nu);
parameters_mut.insert("delta".to_string(), delta);
self.parameters = Some(parameters_mut);
let elapsed = now.elapsed();
elapsed_time(elapsed);
return (0, Some(y)); }
info!(
"LM parameters updated for next attempt: mu = {}, nu = {}",
mu, nu
);
}
}
unreachable!("Inner loop should always return via accept or max rejections")
}
pub fn preloop_trust_region_Nielsen(&mut self) -> Result<(), String> {
if self.i != 0 {
return Ok(()); }
info!("Initializing trust region parameters at iteration 0");
let parameters = self.parameters.clone().unwrap();
let tau = parameters.get("tau").unwrap();
let scaling_method = self.scaling_method.clone().unwrap();
let y = self.y.clone();
let Fy = self.evaluate_function(y.clone());
let Jy = self.evaluate_jacobian(y.clone());
let diag = TrustRegionScaling::init_scaling(&Jy, &scaling_method);
TrustRegionScaling::store_scaling_in_vector(&mut self.scales_vec, &diag, self.i);
info!("Initial scaling diagonal: {:?}", diag.as_slice());
let JtJ = Jy.transpose() * &Jy;
let mu0 = if scaling_method == ScalingMethod::Levenberg {
tau * JtJ.diagonal().max()
} else {
let scaled_diag = JtJ.diagonal().component_mul(&diag).component_mul(&diag);
tau * scaled_diag.max()
};
let scaled_norm = scaled_norm_common(&diag, &y);
let delta0 = 0.3 * (1.0_f64).max(scaled_norm);
let nu0 = 2.0;
let mut parameters_mut = self.parameters.clone().unwrap();
parameters_mut.insert("mu".to_string(), mu0);
parameters_mut.insert("nu".to_string(), nu0);
parameters_mut.insert("delta".to_string(), delta0);
self.parameters = Some(parameters_mut);
info!(
"Initialized: mu0 = {}, nu0 = {}, delta0 = {}",
mu0, nu0, delta0
);
Ok(())
}
}