use nalgebra::{DMatrix, DVector};
pub trait LeastSquaresProblem {
fn set_params(&mut self, x: &DVector<f64>);
fn params(&self) -> DVector<f64>;
fn residuals(&self) -> Option<DVector<f64>>;
fn jacobian(&self) -> Option<DMatrix<f64>>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::numerical::optimization::LM_optimization::LevenbergMarquardt;
#[derive(Clone)]
struct QuadraticProblem {
params: DVector<f64>,
a: DMatrix<f64>,
b: DVector<f64>,
}
impl QuadraticProblem {
fn new() -> Self {
Self {
params: DVector::from_vec(vec![0.0, 0.0]),
a: DMatrix::from_row_slice(3, 2, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]),
b: DVector::from_vec(vec![1.0, 2.0, 3.0]),
}
}
}
impl LeastSquaresProblem for QuadraticProblem {
fn set_params(&mut self, x: &DVector<f64>) {
self.params.copy_from(x);
}
fn params(&self) -> DVector<f64> {
self.params.clone()
}
fn residuals(&self) -> Option<DVector<f64>> {
Some(&self.a * &self.params - &self.b)
}
fn jacobian(&self) -> Option<DMatrix<f64>> {
Some(self.a.clone())
}
}
#[derive(Clone)]
struct RosenbrockProblem {
params: DVector<f64>,
a: f64,
b: f64,
}
impl RosenbrockProblem {
fn new() -> Self {
Self {
params: DVector::from_vec(vec![-1.2, 1.0]), a: 1.0,
b: 100.0,
}
}
}
impl LeastSquaresProblem for RosenbrockProblem {
fn set_params(&mut self, x: &DVector<f64>) {
self.params.copy_from(x);
}
fn params(&self) -> DVector<f64> {
self.params.clone()
}
fn residuals(&self) -> Option<DVector<f64>> {
let x = self.params[0];
let y = self.params[1];
Some(DVector::from_vec(vec![
self.a - x,
(self.b).sqrt() * (y - x * x),
]))
}
fn jacobian(&self) -> Option<DMatrix<f64>> {
let x = self.params[0];
Some(DMatrix::from_row_slice(
2,
2,
&[-1.0, 0.0, -2.0 * x * (self.b).sqrt(), (self.b).sqrt()],
))
}
}
#[derive(Clone)]
struct ExponentialFitProblem {
params: DVector<f64>,
x_data: DVector<f64>,
y_data: DVector<f64>,
}
impl ExponentialFitProblem {
fn new() -> Self {
let x_data = DVector::from_vec(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
let y_data = DVector::from_vec(vec![2.1, 3.2, 5.8, 9.1, 14.8]);
Self {
params: DVector::from_vec(vec![1.0, 1.0]), x_data,
y_data,
}
}
}
impl LeastSquaresProblem for ExponentialFitProblem {
fn set_params(&mut self, x: &DVector<f64>) {
self.params.copy_from(x);
}
fn params(&self) -> DVector<f64> {
self.params.clone()
}
fn residuals(&self) -> Option<DVector<f64>> {
let a = self.params[0];
let b = self.params[1];
let mut residuals = DVector::zeros(self.x_data.len());
for i in 0..self.x_data.len() {
let x = self.x_data[i];
let y_pred = a * (b * x).exp();
residuals[i] = y_pred - self.y_data[i];
}
Some(residuals)
}
fn jacobian(&self) -> Option<DMatrix<f64>> {
let a = self.params[0];
let b = self.params[1];
let mut jacobian = DMatrix::zeros(self.x_data.len(), 2);
for i in 0..self.x_data.len() {
let x = self.x_data[i];
let exp_bx = (b * x).exp();
jacobian[(i, 0)] = exp_bx; jacobian[(i, 1)] = a * x * exp_bx; }
Some(jacobian)
}
}
#[derive(Clone)]
struct PowellProblem {
params: DVector<f64>,
}
impl PowellProblem {
fn new() -> Self {
Self {
params: DVector::from_vec(vec![3.0, -1.0, 0.0, 1.0]),
}
}
}
impl LeastSquaresProblem for PowellProblem {
fn set_params(&mut self, x: &DVector<f64>) {
self.params.copy_from(x);
}
fn params(&self) -> DVector<f64> {
self.params.clone()
}
fn residuals(&self) -> Option<DVector<f64>> {
let x1 = self.params[0];
let x2 = self.params[1];
let x3 = self.params[2];
let x4 = self.params[3];
Some(DVector::from_vec(vec![
x1 + 10.0 * x2,
(5.0_f64).sqrt() * (x3 - x4),
(x2 - 2.0 * x3).powi(2),
(10.0_f64).sqrt() * (x1 - x4).powi(2),
]))
}
fn jacobian(&self) -> Option<DMatrix<f64>> {
let x1 = self.params[0];
let x2 = self.params[1];
let x3 = self.params[2];
let x4 = self.params[3];
Some(DMatrix::from_row_slice(
4,
4,
&[
1.0,
10.0,
0.0,
0.0,
0.0,
0.0,
(5.0_f64).sqrt(),
-(5.0_f64).sqrt(),
0.0,
2.0 * (x2 - 2.0 * x3),
-4.0 * (x2 - 2.0 * x3),
0.0,
2.0 * (10.0_f64).sqrt() * (x1 - x4),
0.0,
0.0,
-2.0 * (10.0_f64).sqrt() * (x1 - x4),
],
))
}
}
#[test]
fn test_quadratic_problem() {
let problem = QuadraticProblem::new();
let (result, report) = LevenbergMarquardt::new().minimize(problem);
println!("Quadratic Problem:");
println!("Termination: {:?}", report.termination);
println!("Evaluations: {}", report.number_of_evaluations);
println!("Final objective: {}", report.objective_function);
println!("Final params: {:?}", result.params());
assert!(report.termination.was_successful());
}
#[test]
fn test_rosenbrock_problem() {
let problem = RosenbrockProblem::new();
let (result, report) = LevenbergMarquardt::new().with_tol(1e-12).minimize(problem);
println!("\nRosenbrock Problem:");
println!("Termination: {:?}", report.termination);
println!("Evaluations: {}", report.number_of_evaluations);
println!("Final objective: {}", report.objective_function);
println!("Final params: {:?}", result.params());
let final_params = result.params();
assert!((final_params[0] - 1.0).abs() < 1e-6);
assert!((final_params[1] - 1.0).abs() < 1e-6);
}
#[test]
fn test_exponential_fit_problem() {
let problem = ExponentialFitProblem::new();
let (result, report) = LevenbergMarquardt::new().minimize(problem);
println!("\nExponential Fit Problem:");
println!("Termination: {:?}", report.termination);
println!("Evaluations: {}", report.number_of_evaluations);
println!("Final objective: {}", report.objective_function);
println!("Final params: {:?}", result.params());
let final_params = result.params();
assert!((final_params[0] - 2.0).abs() < 0.5);
assert!((final_params[1] - 0.5).abs() < 0.5);
}
#[test]
fn test_powell_problem() {
let problem = PowellProblem::new();
let (result, report) = LevenbergMarquardt::new().with_tol(1e-10).minimize(problem);
println!("\nPowell Problem:");
println!("Termination: {:?}", report.termination);
println!("Evaluations: {}", report.number_of_evaluations);
println!("Final objective: {}", report.objective_function);
println!("Final params: {:?}", result.params());
let final_params = result.params();
for ¶m in final_params.iter() {
assert!(param.abs() < 1e-6);
}
}
#[test]
fn test_simple_linear_system() {
#[derive(Clone)]
struct LinearSystem {
params: DVector<f64>,
}
impl LinearSystem {
fn new() -> Self {
Self {
params: DVector::from_vec(vec![0.0, 0.0]),
}
}
}
impl LeastSquaresProblem for LinearSystem {
fn set_params(&mut self, x: &DVector<f64>) {
self.params.copy_from(x);
}
fn params(&self) -> DVector<f64> {
self.params.clone()
}
fn residuals(&self) -> Option<DVector<f64>> {
let x1 = self.params[0];
let x2 = self.params[1];
Some(DVector::from_vec(vec![
2.0 * x1 + x2 - 3.0,
x1 + 3.0 * x2 - 4.0,
]))
}
fn jacobian(&self) -> Option<DMatrix<f64>> {
Some(DMatrix::from_row_slice(2, 2, &[2.0, 1.0, 1.0, 3.0]))
}
}
let problem = LinearSystem::new();
let (result, report) = LevenbergMarquardt::new().minimize(problem);
println!("\nLinear System Problem:");
println!("Termination: {:?}", report.termination);
println!("Evaluations: {}", report.number_of_evaluations);
println!("Final objective: {}", report.objective_function);
println!("Final params: {:?}", result.params());
let final_params = result.params();
assert!((final_params[0] - 1.0).abs() < 1e-10);
assert!((final_params[1] - 1.0).abs() < 1e-10);
assert!(report.objective_function < 1e-20);
}
}