use std::borrow::Cow;
use log::{info, warn};
use nalgebra::{DMatrix, DVector};
use crate::numerical::Nonlinear_systems::engine::{
IterationState, MethodWorkspace, NonlinearMethod, RuntimeDiagnostics, SolveOptions,
StepOutcome, eval_jacobian_with_runtime, eval_residual_with_runtime,
measure_linear_system_operation_owned, scaled_norm, scaling_vector,
};
use crate::numerical::Nonlinear_systems::error::{SolveError, TerminationReason};
use crate::numerical::Nonlinear_systems::problem::JacobianProvider;
const ONE_THIRD: f64 = 1.0 / 3.0;
use crate::numerical::Nonlinear_systems::LM_utils::*;
#[derive(Debug, Clone, Copy)]
pub struct NielsenLevenbergMarquardtMethodAdvanced {
pub tau: f64,
pub nu_init: f64,
pub factor_up: f64,
pub factor_down: f64,
pub rho_threshold: f64,
pub f_tolerance: Option<f64>,
pub g_tolerance: Option<f64>,
pub scaling_method: ScalingMethod,
pub reduction_ratio_method: ReductionRatio,
pub max_rejections: usize,
}
impl Default for NielsenLevenbergMarquardtMethodAdvanced {
fn default() -> Self {
Self {
tau: 1e-6,
nu_init: 2.0,
factor_up: 3.0,
factor_down: 2.0,
rho_threshold: 1e-4,
f_tolerance: None,
g_tolerance: None,
scaling_method: ScalingMethod::Marquardt,
reduction_ratio_method: ReductionRatio::Nielsen,
max_rejections: 15,
}
}
}
#[derive(Debug, Clone)]
pub struct NielsenLevenbergMarquardtStateAdvanced {
mu: f64,
nu: f64,
delta: f64,
scaling: DVector<f64>,
}
impl NonlinearMethod for NielsenLevenbergMarquardtMethodAdvanced {
type MethodState = NielsenLevenbergMarquardtStateAdvanced;
fn init<P: JacobianProvider>(
&self,
_problem: &P,
x0: &DVector<f64>,
_options: &SolveOptions,
residual: &DVector<f64>,
jacobian: &DMatrix<f64>,
) -> Result<Self::MethodState, SolveError> {
if self.tau <= 0.0 || self.nu_init <= 0.0 || self.max_rejections == 0 {
return Err(SolveError::InvalidConfig(
"invalid Nielsen LM Advanced parameters".to_string(),
));
}
let scaling = TrustRegionScaling::init_scaling(jacobian, &self.scaling_method);
let jtj = jacobian.transpose() * jacobian;
let mu = self.tau
* if self.scaling_method == ScalingMethod::Levenberg {
jtj.diagonal().max()
} else {
jtj.diagonal()
.component_mul(&scaling)
.component_mul(&scaling)
.max()
};
if !residual.norm().is_finite() {
return Err(SolveError::NumericalBreakdown(
"initial residual norm is not finite".to_string(),
));
}
let delta = 0.3 * (1.0f64).max(scaled_norm_common(&scaling, x0));
info!(
"Initialized Nielsen LM Advanced with {} scaling",
self.scaling_method
);
info!(" mu0 = {}, nu0 = {}, delta0 = {}", mu, self.nu_init, delta);
info!(" Reduction ratio method: {}", self.reduction_ratio_method);
Ok(NielsenLevenbergMarquardtStateAdvanced {
mu: mu.max(1e-16),
nu: self.nu_init,
delta,
scaling,
})
}
fn step<P: JacobianProvider>(
&self,
problem: &P,
state: &IterationState,
method_state: &mut Self::MethodState,
options: &SolveOptions,
runtime: &mut RuntimeDiagnostics,
) -> Result<StepOutcome, SolveError> {
self.step_impl(problem, state, method_state, options, runtime, None)
}
fn supports_step_workspace(&self) -> bool {
true
}
fn step_with_workspace<P: JacobianProvider>(
&self,
problem: &P,
state: &IterationState,
method_state: &mut Self::MethodState,
options: &SolveOptions,
runtime: &mut RuntimeDiagnostics,
workspace: Option<&mut MethodWorkspace>,
) -> Result<StepOutcome, SolveError> {
self.step_impl(problem, state, method_state, options, runtime, workspace)
}
}
impl NielsenLevenbergMarquardtMethodAdvanced {
fn step_impl<P: JacobianProvider>(
&self,
problem: &P,
state: &IterationState,
method_state: &mut NielsenLevenbergMarquardtStateAdvanced,
options: &SolveOptions,
runtime: &mut RuntimeDiagnostics,
mut workspace: Option<&mut MethodWorkspace>,
) -> Result<StepOutcome, SolveError> {
TrustRegionScaling::update_scaling(
&state.jacobian,
&self.scaling_method,
&mut method_state.scaling,
);
let gradient = -state.jacobian.transpose() * &state.residual;
if let Some(g_tol) = self.g_tolerance {
if gradient.norm() < g_tol {
info!(
"Gradient tolerance satisfied: ||g|| = {:.6e}",
gradient.norm()
);
return Ok(StepOutcome::Terminated(TerminationReason::Converged));
}
}
if let Some(f_tol) = self.f_tolerance {
if state.residual_norm < f_tol {
info!(
"Function tolerance satisfied: ||f|| = {:.6e}",
state.residual_norm
);
return Ok(StepOutcome::Terminated(TerminationReason::Converged));
}
}
let jtj = state.jacobian.transpose() * &state.jacobian;
for rejection_count in 0..self.max_rejections {
info!("=== Inner iteration {} ===", rejection_count);
info!(
"Current: mu = {:.6e}, nu = {:.6e}, delta = {:.6e}",
method_state.mu, method_state.nu, method_state.delta
);
runtime.linear_solves += 1;
let mut regularized = jtj.clone();
TrustRegionScaling::add_scaled_regularization_in_place(
&mut regularized,
method_state.mu,
&method_state.scaling,
&self.scaling_method,
);
let step = measure_linear_system_operation_owned(
options.linear_solver,
regularized,
&gradient,
runtime,
options.diagnostics.collect_statistics,
)?;
info!("Step vector norm: {:.6e}", step.norm());
let scaled_step_norm = scaled_norm_common(&method_state.scaling, &step);
info!("Scaled step norm = {:.6e}", scaled_step_norm);
if scaled_step_norm < options.tolerance {
info!("Solution found (small step)!");
return Ok(StepOutcome::Terminated(TerminationReason::StepTooSmall));
}
let trial_x = if let Some(workspace) = workspace.as_deref_mut() {
workspace.set_affine_trial(&state.x, 1.0, &step)?;
if let Some(bounds) = &options.bounds {
bounds.project_in_place(workspace.trial_x_mut());
}
Cow::Borrowed(workspace.trial_x())
} else {
let mut trial_x = &state.x + &step;
if let Some(bounds) = &options.bounds {
bounds.project_in_place(&mut trial_x);
}
Cow::Owned(trial_x)
};
let trial_residual = eval_residual_with_runtime(
problem,
&trial_x,
runtime,
options.diagnostics.collect_statistics,
)?;
let rho = ReductionRatioSolver::solve_reduction_ratio(
&state.jacobian,
&state.residual,
&trial_residual,
&method_state.scaling,
&step,
method_state.mu,
self.reduction_ratio_method.clone(),
);
info!("Step norm = {:.6e}, rho = {:.6e}", step.norm(), rho);
if rho > 0.75 {
method_state.delta *= self.factor_up;
info!(
"Excellent step (rho > 0.75): expanding delta to {:.6e}",
method_state.delta
);
} else if rho < 0.25 {
method_state.delta /= self.factor_down;
info!(
"Poor step (rho < 0.25): shrinking delta to {:.6e}",
method_state.delta
);
}
if rho > self.rho_threshold {
method_state.mu *= (ONE_THIRD).max(1.0 - (2.0 * rho - 1.0).powi(3));
method_state.mu = method_state.mu.max(1e-16);
method_state.nu = self.nu_init;
info!(
"Step ACCEPTED: mu updated to {:.6e}, nu reset to {:.6e}",
method_state.mu, method_state.nu
);
runtime.accepted_steps += 1;
return Ok(StepOutcome::Continue {
next_x: trial_x.into_owned(),
accepted: true,
});
} else {
info!(
"Step REJECTED (rho <= {}): consecutive rejections = {}",
self.rho_threshold,
rejection_count + 1
);
method_state.mu *= method_state.nu;
method_state.nu *= 2.0;
info!(
"LM parameters updated for next attempt: mu = {:.6e}, nu = {:.6e}",
method_state.mu, method_state.nu
);
runtime.rejected_steps += 1;
}
}
warn!(
"Too many consecutive rejections ({}). No progress possible.",
self.max_rejections
);
Ok(StepOutcome::Terminated(
TerminationReason::RejectedStepLimit,
))
}
}
#[derive(Debug, Clone, Copy)]
pub struct NielsenLevenbergMarquardtMethod {
pub tau: f64,
pub nu_init: f64,
pub factor_up: f64,
pub factor_down: f64,
pub rho_threshold: f64,
pub f_tolerance: Option<f64>,
pub g_tolerance: Option<f64>,
pub use_column_scaling: bool,
pub max_rejections: usize,
}
impl Default for NielsenLevenbergMarquardtMethod {
fn default() -> Self {
Self {
tau: 1e-6,
nu_init: 2.0,
factor_up: 3.0,
factor_down: 2.0,
rho_threshold: 1e-4,
f_tolerance: None,
g_tolerance: None,
use_column_scaling: true,
max_rejections: 15,
}
}
}
#[derive(Debug, Clone)]
pub struct NielsenLevenbergMarquardtState {
mu: f64,
nu: f64,
delta: f64,
}
impl NonlinearMethod for NielsenLevenbergMarquardtMethod {
type MethodState = NielsenLevenbergMarquardtState;
fn init<P: JacobianProvider>(
&self,
_problem: &P,
x0: &DVector<f64>,
_options: &SolveOptions,
residual: &DVector<f64>,
jacobian: &DMatrix<f64>,
) -> Result<Self::MethodState, SolveError> {
if self.tau <= 0.0 || self.nu_init <= 0.0 || self.max_rejections == 0 {
return Err(SolveError::InvalidConfig(
"invalid Nielsen LM parameters".to_string(),
));
}
let scaling = scaling_vector(jacobian, self.use_column_scaling);
let jtj = jacobian.transpose() * jacobian;
let mu = self.tau
* if self.use_column_scaling {
jtj.diagonal()
.component_mul(&scaling)
.component_mul(&scaling)
.max()
} else {
jtj.diagonal().max()
};
if !residual.norm().is_finite() {
return Err(SolveError::NumericalBreakdown(
"initial residual norm is not finite".to_string(),
));
}
Ok(NielsenLevenbergMarquardtState {
mu: mu.max(1e-16),
nu: self.nu_init,
delta: 0.3 * (1.0f64).max(scaled_norm(&scaling, x0)),
})
}
fn step<P: JacobianProvider>(
&self,
problem: &P,
state: &IterationState,
method_state: &mut Self::MethodState,
options: &SolveOptions,
runtime: &mut RuntimeDiagnostics,
) -> Result<StepOutcome, SolveError> {
self.step_impl(problem, state, method_state, options, runtime, None)
}
fn supports_step_workspace(&self) -> bool {
true
}
fn step_with_workspace<P: JacobianProvider>(
&self,
problem: &P,
state: &IterationState,
method_state: &mut Self::MethodState,
options: &SolveOptions,
runtime: &mut RuntimeDiagnostics,
workspace: Option<&mut MethodWorkspace>,
) -> Result<StepOutcome, SolveError> {
self.step_impl(problem, state, method_state, options, runtime, workspace)
}
}
impl NielsenLevenbergMarquardtMethod {
fn step_impl<P: JacobianProvider>(
&self,
problem: &P,
state: &IterationState,
method_state: &mut NielsenLevenbergMarquardtState,
options: &SolveOptions,
runtime: &mut RuntimeDiagnostics,
mut workspace: Option<&mut MethodWorkspace>,
) -> Result<StepOutcome, SolveError> {
let scaling = scaling_vector(&state.jacobian, self.use_column_scaling);
let gradient = -state.jacobian.transpose() * &state.residual;
if let Some(g_tol) = self.g_tolerance {
if gradient.norm() < g_tol {
return Ok(StepOutcome::Terminated(TerminationReason::Converged));
}
}
if let Some(f_tol) = self.f_tolerance {
if state.residual_norm < f_tol {
return Ok(StepOutcome::Terminated(TerminationReason::Converged));
}
}
let jtj = state.jacobian.transpose() * &state.jacobian;
for _ in 0..self.max_rejections {
runtime.linear_solves += 1;
let mut regularized = jtj.clone();
TrustRegionScaling::add_squared_diagonal_regularization_in_place(
&mut regularized,
method_state.mu,
&scaling,
);
let step = measure_linear_system_operation_owned(
options.linear_solver,
regularized,
&gradient,
runtime,
options.diagnostics.collect_statistics,
)?;
if scaled_norm(&scaling, &step) < options.tolerance {
return Ok(StepOutcome::Terminated(TerminationReason::StepTooSmall));
}
let trial_x = if let Some(workspace) = workspace.as_deref_mut() {
workspace.set_affine_trial(&state.x, 1.0, &step)?;
if let Some(bounds) = &options.bounds {
bounds.project_in_place(workspace.trial_x_mut());
}
Cow::Borrowed(workspace.trial_x())
} else {
let mut trial_x = &state.x + &step;
if let Some(bounds) = &options.bounds {
bounds.project_in_place(&mut trial_x);
}
Cow::Owned(trial_x)
};
let trial_residual = eval_residual_with_runtime(
problem,
&trial_x,
runtime,
options.diagnostics.collect_statistics,
)?;
let actual = state.residual.norm_squared() - trial_residual.norm_squared();
let trial_jacobian = eval_jacobian_with_runtime(
problem,
&trial_x,
runtime,
options.diagnostics.collect_statistics,
)?;
let predicted = 0.5
* step.dot(
&(step.component_mul(&scaling).map(|v| method_state.mu * v)
- trial_jacobian.transpose() * &trial_residual),
);
let rho = if predicted.abs() > 1e-12 {
actual / predicted
} else {
0.0
};
if rho > 0.75 {
method_state.delta *= self.factor_up;
} else if rho < 0.25 {
method_state.delta /= self.factor_down;
}
if rho > self.rho_threshold {
method_state.mu *= (1.0f64 / 3.0).max(1.0 - (2.0 * rho - 1.0).powi(3));
method_state.mu = method_state.mu.max(1e-16);
method_state.nu = self.nu_init;
runtime.accepted_steps += 1;
return Ok(StepOutcome::Continue {
next_x: trial_x.into_owned(),
accepted: true,
});
}
runtime.rejected_steps += 1;
method_state.mu *= method_state.nu;
method_state.nu *= 2.0;
}
Ok(StepOutcome::Terminated(
TerminationReason::RejectedStepLimit,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::numerical::Nonlinear_systems::engine::{SolveOptions, SolverEngine};
use crate::numerical::Nonlinear_systems::error::TerminationReason;
use crate::numerical::Nonlinear_systems::problem::{
Bounds, JacobianProvider, NonlinearProblem,
};
use crate::numerical::Nonlinear_systems::symbolic::SymbolicNonlinearProblem;
use approx::assert_relative_eq;
struct RosenbrockProblem;
impl NonlinearProblem for RosenbrockProblem {
fn dimension(&self) -> usize {
2
}
fn residual(&self, x: &DVector<f64>) -> Result<DVector<f64>, SolveError> {
Ok(DVector::from_vec(vec![
10.0 * (x[1] - x[0] * x[0]),
1.0 - x[0],
]))
}
}
impl JacobianProvider for RosenbrockProblem {
fn jacobian(&self, x: &DVector<f64>) -> Result<DMatrix<f64>, SolveError> {
Ok(DMatrix::from_row_slice(
2,
2,
&[-20.0 * x[0], 10.0, -1.0, 0.0],
))
}
}
#[test]
fn nielsen_lm_advanced_converges_rosenbrock() {
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(NielsenLevenbergMarquardtMethodAdvanced::default(), options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
assert_relative_eq!(result.x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], 1.0, epsilon = 1e-5);
}
#[test]
fn nielsen_lm_advanced_with_bounds() {
let bounds = Bounds::new(vec![(0.0, 2.0), (0.0, 2.0)]).expect("bounds");
let options = SolveOptions {
bounds: Some(bounds),
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(NielsenLevenbergMarquardtMethodAdvanced::default(), options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
assert!(result.x[0] >= 0.0 && result.x[0] <= 2.0);
assert!(result.x[1] >= 0.0 && result.x[1] <= 2.0);
assert_relative_eq!(result.x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], 1.0, epsilon = 1e-5);
}
#[test]
fn nielsen_lm_advanced_with_symbolic_problem() {
let problem = SymbolicNonlinearProblem::from_strings(
vec!["x^2+y^2-10".to_string(), "x-y-4".to_string()],
Some(vec!["x".to_string(), "y".to_string()]),
None,
None,
)
.expect("symbolic problem");
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(NielsenLevenbergMarquardtMethodAdvanced::default(), options)
.solve(&problem, DVector::from_vec(vec![1.0, 1.0]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
assert_relative_eq!(result.x[0], 3.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], -1.0, epsilon = 1e-5);
}
#[test]
fn nielsen_lm_advanced_with_gradient_tolerance() {
let mut method = NielsenLevenbergMarquardtMethodAdvanced::default();
method.g_tolerance = Some(1e-8);
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(method, options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
}
#[test]
fn nielsen_lm_advanced_with_function_tolerance() {
let mut method = NielsenLevenbergMarquardtMethodAdvanced::default();
method.f_tolerance = Some(1e-8);
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(method, options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
}
#[test]
fn nielsen_lm_advanced_without_column_scaling() {
let mut method = NielsenLevenbergMarquardtMethodAdvanced::default();
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(method, options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
assert_relative_eq!(result.x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], 1.0, epsilon = 1e-5);
}
#[test]
fn nielsen_lm_advanced_custom_parameters() {
let method = NielsenLevenbergMarquardtMethodAdvanced {
tau: 1e-3,
nu_init: 3.0,
factor_up: 2.5,
factor_down: 1.5,
rho_threshold: 1e-3,
f_tolerance: None,
g_tolerance: None,
max_rejections: 20,
scaling_method: ScalingMethod::More,
reduction_ratio_method: ReductionRatio::More,
};
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(method, options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
assert_relative_eq!(result.x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], 1.0, epsilon = 1e-5);
}
#[test]
fn nielsen_lm_advanced_handles_difficult_start() {
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 200,
..SolveOptions::default()
};
let result = SolverEngine::new(NielsenLevenbergMarquardtMethodAdvanced::default(), options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![-1.0, 2.0]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
assert_relative_eq!(result.x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], 1.0, epsilon = 1e-5);
}
#[test]
fn nielsen_lm_advanced_with_levenberg_scaling() {
let mut method = NielsenLevenbergMarquardtMethodAdvanced::default();
method.scaling_method = ScalingMethod::Levenberg;
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(method, options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
assert_relative_eq!(result.x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], 1.0, epsilon = 1e-5);
}
#[test]
fn nielsen_lm_advanced_with_more_scaling() {
let mut method = NielsenLevenbergMarquardtMethodAdvanced::default();
method.scaling_method = ScalingMethod::More;
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(method, options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
assert_relative_eq!(result.x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], 1.0, epsilon = 1e-5);
}
#[test]
fn nielsen_lm_advanced_with_more_reduction_ratio() {
let mut method = NielsenLevenbergMarquardtMethodAdvanced::default();
method.reduction_ratio_method = ReductionRatio::More;
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(method, options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
assert_relative_eq!(result.x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], 1.0, epsilon = 1e-5);
}
#[test]
fn nielsen_lm_advanced_levenberg_with_more_ratio() {
let mut method = NielsenLevenbergMarquardtMethodAdvanced::default();
method.scaling_method = ScalingMethod::Levenberg;
method.reduction_ratio_method = ReductionRatio::More;
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(method, options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
assert_relative_eq!(result.x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], 1.0, epsilon = 1e-5);
}
#[test]
fn nielsen_lm_advanced_more_scaling_nielsen_ratio() {
let mut method = NielsenLevenbergMarquardtMethodAdvanced::default();
method.scaling_method = ScalingMethod::More;
method.reduction_ratio_method = ReductionRatio::Nielsen;
let options = SolveOptions {
tolerance: 1e-6,
max_iterations: 100,
..SolveOptions::default()
};
let result = SolverEngine::new(method, options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect("solve");
assert_eq!(result.termination, TerminationReason::Converged);
assert_relative_eq!(result.x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], 1.0, epsilon = 1e-5);
}
#[test]
fn nielsen_lm_advanced_all_combinations_with_bounds() {
let bounds = Bounds::new(vec![(0.0, 2.0), (0.0, 2.0)]).expect("bounds");
for scaling in [
ScalingMethod::Levenberg,
ScalingMethod::Marquardt,
ScalingMethod::More,
] {
for ratio in [ReductionRatio::Nielsen, ReductionRatio::More] {
let mut method = NielsenLevenbergMarquardtMethodAdvanced::default();
method.scaling_method = scaling.clone();
method.reduction_ratio_method = ratio.clone();
let options = SolveOptions {
bounds: Some(bounds.clone()),
tolerance: 1e-6,
max_iterations: 150,
..SolveOptions::default()
};
let result = SolverEngine::new(method, options)
.solve(&RosenbrockProblem, DVector::from_vec(vec![0.5, 0.5]))
.expect(&format!(
"solve with {:?} scaling and {:?} ratio",
scaling, ratio
));
assert_eq!(
result.termination,
TerminationReason::Converged,
"Failed with {:?} scaling and {:?} ratio",
scaling,
ratio
);
assert_relative_eq!(result.x[0], 1.0, epsilon = 1e-5);
assert_relative_eq!(result.x[1], 1.0, epsilon = 1e-5);
}
}
}
}