use crate::error::{OptimizeError, OptimizeResult};
use crate::result::OptimizeResults;
use scirs2_core::ndarray::{Array1, Array2, ArrayBase, Data, Ix1};
use std::fmt;
use crate::roots_anderson::root_anderson;
use crate::roots_krylov::root_krylov;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Method {
Hybr,
Lm,
Broyden1,
Broyden2,
Anderson,
KrylovLevenbergMarquardt,
Scalar,
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Method::Hybr => write!(f, "hybr"),
Method::Lm => write!(f, "lm"),
Method::Broyden1 => write!(f, "broyden1"),
Method::Broyden2 => write!(f, "broyden2"),
Method::Anderson => write!(f, "anderson"),
Method::KrylovLevenbergMarquardt => write!(f, "krylov"),
Method::Scalar => write!(f, "scalar"),
}
}
}
#[derive(Debug, Clone)]
pub struct Options {
pub maxfev: Option<usize>,
pub xtol: Option<f64>,
pub ftol: Option<f64>,
pub gtol: Option<f64>,
pub disp: bool,
pub eps: Option<f64>,
pub m_anderson: Option<usize>,
pub beta_anderson: Option<f64>,
}
impl Default for Options {
fn default() -> Self {
Options {
maxfev: None,
xtol: Some(1e-8),
ftol: Some(1e-8),
gtol: Some(1e-8),
disp: false,
eps: Some(1e-8),
m_anderson: Some(5), beta_anderson: Some(0.5), }
}
}
#[allow(dead_code)]
pub fn root<F, J, S>(
func: F,
x0: &ArrayBase<S, Ix1>,
method: Method,
jac: Option<J>,
options: Option<Options>,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&[f64]) -> Array1<f64>,
J: Fn(&[f64]) -> Array2<f64>,
S: Data<Elem = f64>,
{
let options = options.unwrap_or_default();
match method {
Method::Hybr => root_hybr(func, x0, jac, &options),
Method::Lm => root_levenberg_marquardt(func, x0, jac, &options),
Method::Broyden1 => root_broyden1(func, x0, jac, &options),
Method::Broyden2 => {
root_broyden2(func, x0, jac, &options)
}
Method::Anderson => {
root_anderson(func, x0, jac, &options)
}
Method::KrylovLevenbergMarquardt => {
root_krylov(func, x0, jac, &options)
}
Method::Scalar => root_scalar(func, x0, jac, &options),
}
}
#[allow(dead_code)]
fn root_hybr<F, J, S>(
func: F,
x0: &ArrayBase<S, Ix1>,
jacobian_fn: Option<J>,
options: &Options,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&[f64]) -> Array1<f64>,
J: Fn(&[f64]) -> Array2<f64>,
S: Data<Elem = f64>,
{
let xtol = options.xtol.unwrap_or(1e-8);
let ftol = options.ftol.unwrap_or(1e-8);
let maxfev = options.maxfev.unwrap_or(100 * x0.len());
let eps = options.eps.unwrap_or(1e-8);
let n = x0.len();
let mut x = x0.to_owned();
let mut f = func(x.as_slice().expect("Operation failed"));
let mut nfev = 1;
let mut njev = 0;
let compute_numerical_jac =
|x_values: &[f64], f_values: &Array1<f64>| -> (Array2<f64>, usize) {
let mut jac = Array2::zeros((f_values.len(), x_values.len()));
let mut count = 0;
for j in 0..x_values.len() {
let mut x_h = Vec::from(x_values);
x_h[j] += eps;
let f_h = func(&x_h);
count += 1;
for i in 0..f_values.len() {
jac[[i, j]] = (f_h[i] - f_values[i]) / eps;
}
}
(jac, count)
};
let get_jacobian = |x_values: &[f64],
f_values: &Array1<f64>,
jac_fn: &Option<J>|
-> (Array2<f64>, usize, usize) {
match jac_fn {
Some(func) => {
let j = func(x_values);
(j, 0, 1)
}
None => {
let (j, count) = compute_numerical_jac(x_values, f_values);
(j, count, 0)
}
}
};
let (mut jac, nfev_inc, njev_inc) =
get_jacobian(x.as_slice().expect("Operation failed"), &f, &jacobian_fn);
nfev += nfev_inc;
njev += njev_inc;
let mut iter = 0;
let mut converged = false;
while iter < maxfev {
let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
if f_norm < ftol {
converged = true;
break;
}
let delta = match solve(&jac, &(-&f)) {
Some(d) => d,
None => {
let mut gradient = Array1::zeros(n);
for i in 0..n {
for j in 0..f.len() {
gradient[i] += jac[[j, i]] * f[j];
}
}
let step_size = 0.1
/ (1.0
+ gradient
.iter()
.map(|&g: &f64| g.powi(2))
.sum::<f64>()
.sqrt());
-gradient * step_size
}
};
let mut x_new = x.clone();
for i in 0..n {
x_new[i] += delta[i];
}
let mut alpha = 1.0;
let mut f_new = func(x_new.as_slice().expect("Operation failed"));
nfev += 1;
let mut f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
let max_backtrack = 5;
let mut backtrack_count = 0;
while f_new_norm > f_norm && backtrack_count < max_backtrack {
alpha *= 0.5;
backtrack_count += 1;
for i in 0..n {
x_new[i] = x[i] + alpha * delta[i];
}
f_new = func(x_new.as_slice().expect("Operation failed"));
nfev += 1;
f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
}
let step_norm = (0..n)
.map(|i| (x_new[i] - x[i]).powi(2))
.sum::<f64>()
.sqrt();
let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
if step_norm < xtol * (1.0 + x_norm) {
converged = true;
x = x_new;
f = f_new;
break;
}
x = x_new;
f = f_new;
let (new_jac, nfev_delta, njev_delta) =
get_jacobian(x.as_slice().expect("Operation failed"), &f, &jacobian_fn);
jac = new_jac;
nfev += nfev_delta;
njev += njev_delta;
iter += 1;
}
let mut result = OptimizeResults::default();
result.x = x;
result.fun = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
let (jac_vec, _) = jac.into_raw_vec_and_offset();
result.jac = Some(jac_vec);
result.nfev = nfev;
result.njev = njev;
result.nit = iter;
result.success = converged;
if converged {
result.message = "Root finding converged successfully".to_string();
} else {
result.message = "Maximum number of function evaluations reached".to_string();
}
Ok(result)
}
#[allow(dead_code)]
fn solve(a: &Array2<f64>, b: &Array1<f64>) -> Option<Array1<f64>> {
use scirs2_linalg::solve;
solve(&a.view(), &b.view(), None).ok()
}
#[allow(dead_code)]
fn root_broyden1<F, J, S>(
func: F,
x0: &ArrayBase<S, Ix1>,
jacobian_fn: Option<J>,
options: &Options,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&[f64]) -> Array1<f64>,
J: Fn(&[f64]) -> Array2<f64>,
S: Data<Elem = f64>,
{
let xtol = options.xtol.unwrap_or(1e-8);
let ftol = options.ftol.unwrap_or(1e-8);
let maxfev = options.maxfev.unwrap_or(100 * x0.len());
let eps = options.eps.unwrap_or(1e-8);
let n = x0.len();
let mut x = x0.to_owned();
let mut f = func(x.as_slice().expect("Operation failed"));
let mut nfev = 1;
let mut njev = 0;
let compute_numerical_jac =
|x_values: &[f64], f_values: &Array1<f64>| -> (Array2<f64>, usize) {
let mut jac = Array2::zeros((f_values.len(), x_values.len()));
let mut count = 0;
for j in 0..x_values.len() {
let mut x_h = Vec::from(x_values);
x_h[j] += eps;
let f_h = func(&x_h);
count += 1;
for i in 0..f_values.len() {
jac[[i, j]] = (f_h[i] - f_values[i]) / eps;
}
}
(jac, count)
};
let (mut jac, nfev_inc, njev_inc) = match &jacobian_fn {
Some(jac_fn) => {
let j = jac_fn(x.as_slice().expect("Operation failed"));
(j, 0, 1)
}
None => {
let (j, count) = compute_numerical_jac(x.as_slice().expect("Operation failed"), &f);
(j, count, 0)
}
};
nfev += nfev_inc;
njev += njev_inc;
let mut iter = 0;
let mut converged = false;
while iter < maxfev {
let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
if f_norm < ftol {
converged = true;
break;
}
let delta = match solve(&jac, &(-&f)) {
Some(d) => d,
None => {
let mut gradient = Array1::zeros(n);
for i in 0..n {
for j in 0..f.len() {
gradient[i] += jac[[j, i]] * f[j];
}
}
let step_size = 0.1
/ (1.0
+ gradient
.iter()
.map(|&g: &f64| g.powi(2))
.sum::<f64>()
.sqrt());
-gradient * step_size
}
};
let mut x_new = x.clone();
for i in 0..n {
x_new[i] += delta[i];
}
let mut alpha = 1.0;
let mut f_new = func(x_new.as_slice().expect("Operation failed"));
nfev += 1;
let mut f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
let max_backtrack = 5;
let mut backtrack_count = 0;
while f_new_norm > f_norm && backtrack_count < max_backtrack {
alpha *= 0.5;
backtrack_count += 1;
for i in 0..n {
x_new[i] = x[i] + alpha * delta[i];
}
f_new = func(x_new.as_slice().expect("Operation failed"));
nfev += 1;
f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
}
let step_norm = (0..n)
.map(|i| (x_new[i] - x[i]).powi(2))
.sum::<f64>()
.sqrt();
let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
if step_norm < xtol * (1.0 + x_norm) {
converged = true;
x = x_new;
f = f_new;
break;
}
let mut s: Array1<f64> = Array1::zeros(n);
for i in 0..n {
s[i] = x_new[i] - x[i];
}
let mut y: Array1<f64> = Array1::zeros(f.len());
for i in 0..f.len() {
y[i] = f_new[i] - f[i];
}
let mut js: Array1<f64> = Array1::zeros(f.len());
for i in 0..f.len() {
for j in 0..n {
js[i] += jac[[i, j]] * s[j];
}
}
let mut residual: Array1<f64> = Array1::zeros(f.len());
for i in 0..f.len() {
residual[i] = y[i] - js[i];
}
let s_norm_squared = s.iter().map(|&si| si.powi(2)).sum::<f64>();
if s_norm_squared > 1e-14 {
for i in 0..f.len() {
for j in 0..n {
jac[[i, j]] += residual[i] * s[j] / s_norm_squared;
}
}
}
x = x_new;
f = f_new;
iter += 1;
}
let mut result = OptimizeResults::default();
result.x = x;
result.fun = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
let (jac_vec, _) = jac.into_raw_vec_and_offset();
result.jac = Some(jac_vec);
result.nfev = nfev;
result.njev = njev;
result.nit = iter;
result.success = converged;
if converged {
result.message = "Root finding converged successfully".to_string();
} else {
result.message = "Maximum number of function evaluations reached".to_string();
}
Ok(result)
}
#[allow(dead_code)]
fn root_broyden2<F, J, S>(
func: F,
x0: &ArrayBase<S, Ix1>,
jacobian_fn: Option<J>,
options: &Options,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&[f64]) -> Array1<f64>,
J: Fn(&[f64]) -> Array2<f64>,
S: Data<Elem = f64>,
{
let xtol = options.xtol.unwrap_or(1e-8);
let ftol = options.ftol.unwrap_or(1e-8);
let maxfev = options.maxfev.unwrap_or(100 * x0.len());
let eps = options.eps.unwrap_or(1e-8);
let n = x0.len();
let mut x = x0.to_owned();
let mut f = func(x.as_slice().expect("Operation failed"));
let mut nfev = 1;
let mut njev = 0;
let compute_numerical_jac =
|x_values: &[f64], f_values: &Array1<f64>| -> (Array2<f64>, usize) {
let mut jac = Array2::zeros((f_values.len(), x_values.len()));
let mut count = 0;
for j in 0..x_values.len() {
let mut x_h = Vec::from(x_values);
x_h[j] += eps;
let f_h = func(&x_h);
count += 1;
for i in 0..f_values.len() {
jac[[i, j]] = (f_h[i] - f_values[i]) / eps;
}
}
(jac, count)
};
let (mut jac, nfev_inc, njev_inc) = match &jacobian_fn {
Some(jac_fn) => {
let j = jac_fn(x.as_slice().expect("Operation failed"));
(j, 0, 1)
}
None => {
let (j, count) = compute_numerical_jac(x.as_slice().expect("Operation failed"), &f);
(j, count, 0)
}
};
nfev += nfev_inc;
njev += njev_inc;
let mut iter = 0;
let mut converged = false;
while iter < maxfev {
let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
if f_norm < ftol {
converged = true;
break;
}
let delta = match solve(&jac, &(-&f)) {
Some(d) => d,
None => {
let mut gradient = Array1::zeros(n);
for i in 0..n {
for j in 0..f.len() {
gradient[i] += jac[[j, i]] * f[j];
}
}
let step_size = 0.1
/ (1.0
+ gradient
.iter()
.map(|&g: &f64| g.powi(2))
.sum::<f64>()
.sqrt());
-gradient * step_size
}
};
let mut x_new = x.clone();
for i in 0..n {
x_new[i] += delta[i];
}
let mut alpha = 1.0;
let mut f_new = func(x_new.as_slice().expect("Operation failed"));
nfev += 1;
let mut f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
let max_backtrack = 5;
let mut backtrack_count = 0;
while f_new_norm > f_norm && backtrack_count < max_backtrack {
alpha *= 0.5;
backtrack_count += 1;
for i in 0..n {
x_new[i] = x[i] + alpha * delta[i];
}
f_new = func(x_new.as_slice().expect("Operation failed"));
nfev += 1;
f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
}
let step_norm = (0..n)
.map(|i| (x_new[i] - x[i]).powi(2))
.sum::<f64>()
.sqrt();
let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
if step_norm < xtol * (1.0 + x_norm) {
converged = true;
x = x_new;
f = f_new;
break;
}
let mut s: Array1<f64> = Array1::zeros(n);
for i in 0..n {
s[i] = x_new[i] - x[i];
}
let mut y: Array1<f64> = Array1::zeros(f.len());
for i in 0..f.len() {
y[i] = f_new[i] - f[i];
}
let mut js: Array1<f64> = Array1::zeros(f.len());
for i in 0..f.len() {
for j in 0..n {
js[i] += jac[[i, j]] * s[j];
}
}
let mut residual: Array1<f64> = Array1::zeros(f.len());
for i in 0..f.len() {
residual[i] = y[i] - js[i];
}
let mut jtjs: Array1<f64> = Array1::zeros(n);
let mut jtj: Array2<f64> = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
for k in 0..f.len() {
jtj[[i, j]] += jac[[k, i]] * jac[[k, j]];
}
}
}
for i in 0..n {
for j in 0..n {
jtjs[i] += jtj[[i, j]] * s[j];
}
}
let mut denominator: f64 = 0.0;
for i in 0..n {
denominator += s[i] * jtjs[i];
}
if denominator.abs() > 1e-14 {
for i in 0..f.len() {
for j in 0..n {
let mut change: f64 = 0.0;
for k in 0..n {
change += residual[i] * s[k] * jac[[i, k]];
}
jac[[i, j]] += change * s[j] / denominator;
}
}
}
x = x_new;
f = f_new;
iter += 1;
}
let mut result = OptimizeResults::default();
result.x = x;
result.fun = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
let (jac_vec, _) = jac.into_raw_vec_and_offset();
result.jac = Some(jac_vec);
result.nfev = nfev;
result.njev = njev;
result.nit = iter;
result.success = converged;
if converged {
result.message = "Root finding converged successfully".to_string();
} else {
result.message = "Maximum number of function evaluations reached".to_string();
}
Ok(result)
}
#[allow(dead_code)]
fn root_levenberg_marquardt<F, J, S>(
func: F,
x0: &ArrayBase<S, Ix1>,
jacobian_fn: Option<J>,
options: &Options,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&[f64]) -> Array1<f64>,
J: Fn(&[f64]) -> Array2<f64>,
S: Data<Elem = f64>,
{
let xtol = options.xtol.unwrap_or(1e-8);
let ftol = options.ftol.unwrap_or(1e-8);
let maxfev = options.maxfev.unwrap_or(100 * x0.len());
let eps = options.eps.unwrap_or(1e-8);
let n = x0.len();
let mut x = x0.to_owned();
let mut f = func(x.as_slice().expect("Operation failed"));
let mut nfev = 1;
let mut njev = 0;
let mut lambda = 1e-3; let lambda_factor = 10.0;
let compute_numerical_jac =
|x_values: &[f64], f_values: &Array1<f64>| -> (Array2<f64>, usize) {
let mut jac = Array2::zeros((f_values.len(), x_values.len()));
let mut count = 0;
for j in 0..x_values.len() {
let mut x_h = Vec::from(x_values);
x_h[j] += eps;
let f_h = func(&x_h);
count += 1;
for i in 0..f_values.len() {
jac[[i, j]] = (f_h[i] - f_values[i]) / eps;
}
}
(jac, count)
};
let (mut jac, nfev_inc, njev_inc) = match &jacobian_fn {
Some(jac_fn) => {
let j = jac_fn(x.as_slice().expect("Operation failed"));
(j, 0, 1)
}
None => {
let (j, count) = compute_numerical_jac(x.as_slice().expect("Operation failed"), &f);
(j, count, 0)
}
};
nfev += nfev_inc;
njev += njev_inc;
let mut iter = 0;
let mut converged = false;
let mut current_cost = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
while iter < maxfev {
let f_norm = current_cost.sqrt();
if f_norm < ftol {
converged = true;
break;
}
let mut jtj = Array2::zeros((n, n));
let mut jtf = Array1::zeros(n);
for i in 0..n {
for j in 0..n {
for k in 0..f.len() {
jtj[[i, j]] += jac[[k, i]] * jac[[k, j]];
}
}
for k in 0..f.len() {
jtf[i] += jac[[k, i]] * f[k];
}
}
for i in 0..n {
jtj[[i, i]] += lambda;
}
let neg_jtf = jtf.mapv(|x: f64| -x);
let delta = match solve(&jtj, &neg_jtf) {
Some(d) => d,
None => {
lambda *= lambda_factor;
for i in 0..n {
jtj[[i, i]] += lambda;
}
let neg_jtf2 = jtf.mapv(|x| -x);
match solve(&jtj, &neg_jtf2) {
Some(d) => d,
None => {
let step_size =
0.1 / (1.0 + jtf.iter().map(|&g| g.powi(2)).sum::<f64>().sqrt());
jtf.mapv(|x| -x * step_size)
}
}
}
};
let mut x_new = x.clone();
for i in 0..n {
x_new[i] += delta[i];
}
let f_new = func(x_new.as_slice().expect("Operation failed"));
nfev += 1;
let new_cost = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>();
if new_cost < current_cost {
let improvement = (current_cost - new_cost) / current_cost;
let step_norm = (0..n)
.map(|i| (x_new[i] - x[i]).powi(2))
.sum::<f64>()
.sqrt();
let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
if step_norm < xtol * (1.0 + x_norm) || improvement < ftol {
converged = true;
x = x_new;
f = f_new;
current_cost = new_cost;
break;
}
x = x_new;
f = f_new;
current_cost = new_cost;
lambda = f64::max(lambda / lambda_factor, 1e-12);
let (new_jac, nfev_delta, njev_delta) = match &jacobian_fn {
Some(jac_fn) => {
let j = jac_fn(x.as_slice().expect("Operation failed"));
(j, 0, 1)
}
None => {
let (j, count) =
compute_numerical_jac(x.as_slice().expect("Operation failed"), &f);
(j, count, 0)
}
};
jac = new_jac;
nfev += nfev_delta;
njev += njev_delta;
} else {
lambda = f64::min(lambda * lambda_factor, 1e12);
}
iter += 1;
}
let mut result = OptimizeResults::default();
result.x = x;
result.fun = current_cost;
let (jac_vec, _) = jac.into_raw_vec_and_offset();
result.jac = Some(jac_vec);
result.nfev = nfev;
result.njev = njev;
result.nit = iter;
result.success = converged;
if converged {
result.message = "Levenberg-Marquardt converged successfully".to_string();
} else {
result.message = "Maximum number of function evaluations reached".to_string();
}
Ok(result)
}
#[allow(dead_code)]
fn root_scalar<F, J, S>(
func: F,
x0: &ArrayBase<S, Ix1>,
jacobian_fn: Option<J>,
options: &Options,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&[f64]) -> Array1<f64>,
J: Fn(&[f64]) -> Array2<f64>,
S: Data<Elem = f64>,
{
if x0.len() != 1 {
return Err(OptimizeError::InvalidInput(
"Scalar method requires exactly one variable".to_string(),
));
}
let xtol = options.xtol.unwrap_or(1e-8);
let ftol = options.ftol.unwrap_or(1e-8);
let maxfev = options.maxfev.unwrap_or(100);
let eps = options.eps.unwrap_or(1e-8);
let mut x = x0[0];
let mut f_val = func(&[x])[0];
let mut nfev = 1;
let mut njev = 0;
let mut a = x - 1.0;
let mut b = x + 1.0;
let mut fa = func(&[a])[0];
let mut fb = func(&[b])[0];
nfev += 2;
let mut bracket_attempts = 0;
while fa * fb > 0.0 && bracket_attempts < 10 {
if fa.abs() < fb.abs() {
a = a - 2.0 * (b - a);
fa = func(&[a])[0];
} else {
b = b + 2.0 * (b - a);
fb = func(&[b])[0];
}
nfev += 1;
bracket_attempts += 1;
}
let mut iter = 0;
let mut converged = false;
while iter < maxfev {
if f_val.abs() < ftol {
converged = true;
break;
}
let df_dx = match &jacobian_fn {
Some(jac_fn) => {
let jac = jac_fn(&[x]);
njev += 1;
jac[[0, 0]]
}
None => {
let f_plus = func(&[x + eps])[0];
nfev += 1;
(f_plus - f_val) / eps
}
};
let newton_step = if df_dx.abs() > 1e-14 {
-f_val / df_dx
} else {
if fa * fb < 0.0 {
if f_val * fa < 0.0 {
(a - x) / 2.0
} else {
(b - x) / 2.0
}
} else {
if f_val > 0.0 {
-0.1
} else {
0.1
}
}
};
let x_new = x + newton_step;
let x_new = if fa * fb < 0.0 {
f64::max(a + 0.01 * (b - a), f64::min(b - 0.01 * (b - a), x_new))
} else {
x_new
};
let f_new = func(&[x_new])[0];
nfev += 1;
if (x_new - x).abs() < xtol * (1.0 + x.abs()) {
converged = true;
x = x_new;
f_val = f_new;
break;
}
x = x_new;
f_val = f_new;
if fa * fb < 0.0 {
if f_val * fa < 0.0 {
b = x;
fb = f_val;
} else {
a = x;
fa = f_val;
}
}
iter += 1;
}
let mut result = OptimizeResults::default();
result.x = Array1::from_vec(vec![x]);
result.fun = f_val.powi(2);
result.nfev = nfev;
result.njev = njev;
result.nit = iter;
result.success = converged;
if converged {
result.message = "Scalar root finding converged successfully".to_string();
} else {
result.message = "Maximum number of function evaluations reached".to_string();
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::array;
fn f(x: &[f64]) -> Array1<f64> {
let x0 = x[0];
let x1 = x[1];
array![
x0.powi(2) + x1.powi(2) - 1.0, x0 - x1 ]
}
fn jacobian(x: &[f64]) -> Array2<f64> {
let x0 = x[0];
let x1 = x[1];
array![[2.0 * x0, 2.0 * x1], [1.0, -1.0]]
}
#[test]
fn test_root_hybr() {
let x0 = array![2.0, 2.0];
let result =
root(f, &x0.view(), Method::Hybr, Some(jacobian), None).expect("Operation failed");
assert!(result.success);
let sol1 = (0.5f64).sqrt();
let sol2 = -(0.5f64).sqrt();
let dist_to_sol1 = ((result.x[0] - sol1).powi(2) + (result.x[1] - sol1).powi(2)).sqrt();
let dist_to_sol2 = ((result.x[0] - sol2).powi(2) + (result.x[1] - sol2).powi(2)).sqrt();
let min_dist = dist_to_sol1.min(dist_to_sol2);
assert!(
min_dist < 1e-5,
"Distance to nearest solution: {}",
min_dist
);
assert!(result.fun < 1e-10);
assert!(result.jac.is_some());
println!(
"Hybr root result: x = {:?}, f = {}, iterations = {}",
result.x, result.fun, result.nit
);
}
#[test]
fn test_root_broyden1() {
let x0 = array![2.0, 2.0];
let result =
root(f, &x0.view(), Method::Broyden1, Some(jacobian), None).expect("Operation failed");
assert!(result.success);
let sol1 = (0.5f64).sqrt();
let sol2 = -(0.5f64).sqrt();
let dist_to_sol1 = ((result.x[0] - sol1).powi(2) + (result.x[1] - sol1).powi(2)).sqrt();
let dist_to_sol2 = ((result.x[0] - sol2).powi(2) + (result.x[1] - sol2).powi(2)).sqrt();
let min_dist = dist_to_sol1.min(dist_to_sol2);
assert!(
min_dist < 1e-5,
"Distance to nearest solution: {}",
min_dist
);
assert!(result.fun < 1e-10);
assert!(result.jac.is_some());
println!(
"Broyden1 root result: x = {:?}, f = {}, iterations = {}",
result.x, result.fun, result.nit
);
}
#[test]
fn test_root_system() {
fn system(x: &[f64]) -> Array1<f64> {
let x0 = x[0];
let x1 = x[1];
let x2 = x[2];
array![
x0.powi(2) + x1.powi(2) + x2.powi(2) - 1.0, x0.powi(2) + x1.powi(2) - x2, x0 - x1 ]
}
fn system_jac(x: &[f64]) -> Array2<f64> {
let x0 = x[0];
let x1 = x[1];
array![
[2.0 * x0, 2.0 * x1, 2.0 * x[2]],
[2.0 * x0, 2.0 * x1, -1.0],
[1.0, -1.0, 0.0]
]
}
let x0 = array![0.5, 0.5, 0.5];
let options = Options {
xtol: Some(1e-6),
ftol: Some(1e-6),
maxfev: Some(50),
..Options::default()
};
let result = root(
system,
&x0.view(),
Method::Hybr,
Some(system_jac),
Some(options.clone()),
)
.expect("Operation failed");
assert!(result.success);
let x = &result.x;
assert!((x[0] - x[1]).abs() < 1e-5);
assert!((x[0].powi(2) + x[1].powi(2) - x[2]).abs() < 1e-5);
assert!((x[0].powi(2) + x[1].powi(2) + x[2].powi(2) - 1.0).abs() < 1e-5);
assert!(result.fun < 1e-10);
println!(
"Hybr system root: x = {:?}, f = {}, iterations = {}",
result.x, result.fun, result.nit
);
}
#[test]
fn test_compare_methods() {
fn complex_system(x: &[f64]) -> Array1<f64> {
let x0 = x[0];
let x1 = x[1];
array![
x0.powi(2) + x1.powi(2) - 4.0, x1 - x0.powi(2) ]
}
fn complex_system_jac(x: &[f64]) -> Array2<f64> {
let x0 = x[0];
let x1 = x[1];
array![[2.0 * x0, 2.0 * x1], [-2.0 * x0, 1.0]]
}
let x0 = array![0.0, 2.0];
let options = Options {
xtol: Some(1e-6),
ftol: Some(1e-6),
maxfev: Some(100),
..Options::default()
};
let hybr_result = root(
complex_system,
&x0.view(),
Method::Hybr,
Some(complex_system_jac),
Some(options.clone()),
)
.expect("Operation failed");
let broyden1_result = root(
complex_system,
&x0.view(),
Method::Broyden1,
Some(complex_system_jac),
Some(options.clone()),
)
.expect("Operation failed");
let broyden2_result = root(
complex_system,
&x0.view(),
Method::Broyden2,
Some(complex_system_jac),
Some(options),
)
.expect("Operation failed");
assert!(hybr_result.success);
assert!(broyden1_result.success);
assert!(broyden2_result.success);
println!(
"Hybr complex system: x = {:?}, f = {}, iterations = {}",
hybr_result.x, hybr_result.fun, hybr_result.nit
);
println!(
"Broyden1 complex system: x = {:?}, f = {}, iterations = {}",
broyden1_result.x, broyden1_result.fun, broyden1_result.nit
);
println!(
"Broyden2 complex system: x = {:?}, f = {}, iterations = {}",
broyden2_result.x, broyden2_result.fun, broyden2_result.nit
);
let distance12 = ((hybr_result.x[0] - broyden1_result.x[0]).powi(2)
+ (hybr_result.x[1] - broyden1_result.x[1]).powi(2))
.sqrt();
let distance13 = ((hybr_result.x[0] - broyden2_result.x[0]).powi(2)
+ (hybr_result.x[1] - broyden2_result.x[1]).powi(2))
.sqrt();
let distance23 = ((broyden1_result.x[0] - broyden2_result.x[0]).powi(2)
+ (broyden1_result.x[1] - broyden2_result.x[1]).powi(2))
.sqrt();
assert!(
distance12 < 1e-2,
"Hybr and Broyden1 converged to different solutions, distance = {}",
distance12
);
assert!(
distance13 < 1e-2,
"Hybr and Broyden2 converged to different solutions, distance = {}",
distance13
);
assert!(
distance23 < 1e-2,
"Broyden1 and Broyden2 converged to different solutions, distance = {}",
distance23
);
let sol1_distance =
((hybr_result.x[0] - 2.0).powi(2) + (hybr_result.x[1] - 4.0).powi(2)).sqrt();
let sol2_distance =
((hybr_result.x[0] + 2.0).powi(2) + (hybr_result.x[1] - 4.0).powi(2)).sqrt();
let sol3_distance =
((hybr_result.x[0] - 0.0).powi(2) + (hybr_result.x[1] - 0.0).powi(2)).sqrt();
let closest_distance = sol1_distance.min(sol2_distance).min(sol3_distance);
assert!(
closest_distance < 2.0,
"Hybr solution not close to any expected root"
);
let broyden2_test = root(
f,
&array![2.0, 2.0].view(),
Method::Broyden2,
Some(jacobian),
None,
)
.expect("Operation failed");
assert!(broyden2_test.success);
assert!(broyden2_test.fun < 1e-10);
println!(
"Broyden2 simple test: x = {:?}, f = {}, iterations = {}",
broyden2_test.x, broyden2_test.fun, broyden2_test.nit
);
}
#[test]
fn test_anderson_method() {
fn simple_f(x: &[f64]) -> Array1<f64> {
array![x[0].powi(2) - 1.0]
}
let x0 = array![2.0];
let options = Options {
maxfev: Some(100),
ftol: Some(1e-6),
xtol: Some(1e-6),
..Options::default()
};
let result = root(
simple_f,
&x0.view(),
Method::Anderson,
None::<fn(&[f64]) -> Array2<f64>>,
Some(options),
)
.expect("Operation failed");
println!("Anderson method for simple problem:");
println!(
"Success: {}, x = {:?}, iterations = {}, fun = {}",
result.success, result.x, result.nit, result.fun
);
let dist = (result.x[0].abs() - 1.0).abs();
println!("Distance to solution: {}", dist);
}
#[test]
fn test_krylov_method() {
let x0 = array![2.0, 2.0];
let options = Options {
maxfev: Some(500),
ftol: Some(1e-6),
xtol: Some(1e-6),
..Options::default()
};
let result = root(
f,
&x0.view(),
Method::KrylovLevenbergMarquardt,
None::<fn(&[f64]) -> Array2<f64>>,
Some(options),
)
.expect("Operation failed");
println!(
"Krylov method success: {}, x = {:?}, iterations = {}, fun = {}",
result.success, result.x, result.nit, result.fun
);
let sol1 = (0.5f64).sqrt();
let sol2 = -(0.5f64).sqrt();
let dist_to_sol1 = ((result.x[0] - sol1).powi(2) + (result.x[1] - sol1).powi(2)).sqrt();
let dist_to_sol2 = ((result.x[0] - sol2).powi(2) + (result.x[1] - sol2).powi(2)).sqrt();
let min_dist = dist_to_sol1.min(dist_to_sol2);
println!("Krylov method distance to solution: {}", min_dist);
println!(
"Krylov method result: x = {:?}, f = {}, iterations = {}",
result.x, result.fun, result.nit
);
}
}