use nalgebra::{DMatrix, DVector};
use std::f64;
pub fn lmpar(
r: &mut DMatrix<f64>,
ipvt: &[usize],
diag: &DVector<f64>,
qtb: &DVector<f64>,
delta: f64,
par: &mut f64,
) -> Result<(DVector<f64>, DVector<f64>), &'static str> {
let n = r.ncols();
if r.nrows() < n || diag.len() != n || qtb.len() != n || ipvt.len() != n {
return Err("Dimension mismatch in lmpar");
}
const P1: f64 = 0.1;
const P001: f64 = 0.001;
const DWARF: f64 = f64::EPSILON;
const MAX_ITER: usize = 10;
let mut x = DVector::zeros(n);
let mut sdiag = DVector::zeros(n);
let mut wa1 = DVector::zeros(n);
let mut wa2 = DVector::zeros(n);
let (gauss_newton_x, nsing) = compute_gauss_newton_direction(r, ipvt, qtb, &mut wa1)?;
x.copy_from(&gauss_newton_x);
wa2.zip_zip_apply(&x, diag, |wa2_i, x_i, diag_i| *wa2_i = diag_i * x_i);
let dxnorm = wa2.norm();
let mut fp = dxnorm - delta;
if fp <= P1 * delta {
*par = 0.0;
return Ok((x, sdiag));
}
let parl = if nsing >= n {
compute_lower_bound(r, ipvt, diag, &wa2, dxnorm, fp, delta, &mut wa1)?
} else {
0.0
};
let paru = compute_upper_bound(r, ipvt, diag, qtb, delta, &mut wa1)?;
*par = par.max(parl).min(paru);
if *par == 0.0 {
let gnorm = wa1.norm(); *par = gnorm / dxnorm;
}
let mut iter = 0;
loop {
iter += 1;
if *par == 0.0 {
*par = DWARF.max(P001 * paru);
}
let sqrt_par = par.sqrt();
wa1.zip_apply(diag, |wa1_i, diag_i| *wa1_i = sqrt_par * diag_i);
qrsolv(r, ipvt, &wa1, qtb, &mut x, &mut sdiag, &mut wa2)?;
wa2.zip_zip_apply(&x, diag, |wa2_i, x_i, diag_i| *wa2_i = diag_i * x_i);
let new_dxnorm = wa2.norm();
let temp_fp = fp;
fp = new_dxnorm - delta;
if fp.abs() <= P1 * delta
|| (parl == 0.0 && fp <= temp_fp && temp_fp < 0.0)
|| iter >= MAX_ITER
{
break;
}
let parc = compute_newton_correction(
r, ipvt, diag, &sdiag, &wa2, new_dxnorm, fp, delta, &mut wa1,
)?;
let new_parl = if fp > 0.0 { parl.max(*par) } else { parl };
let _new_paru = if fp < 0.0 { paru.min(*par) } else { paru };
*par = new_parl.max(*par + parc);
}
Ok((x, sdiag))
}
fn compute_gauss_newton_direction(
r: &DMatrix<f64>,
ipvt: &[usize],
qtb: &DVector<f64>,
wa1: &mut DVector<f64>,
) -> Result<(DVector<f64>, usize), &'static str> {
let n = r.ncols();
let nsing = r.diagonal().iter().position(|&x| x == 0.0).unwrap_or(n);
wa1.copy_from(qtb);
for i in nsing..n {
wa1[i] = 0.0;
}
if nsing > 0 {
for k in 0..nsing {
let j = nsing - 1 - k;
wa1[j] /= r[(j, j)];
let temp = wa1[j];
for i in 0..j {
wa1[i] -= r[(i, j)] * temp;
}
}
}
let mut x = DVector::zeros(n);
for (j, &pivot_idx) in ipvt.iter().enumerate() {
if pivot_idx > 0 && pivot_idx <= n {
x[pivot_idx - 1] = wa1[j];
}
}
Ok((x, nsing))
}
fn compute_lower_bound(
r: &DMatrix<f64>,
ipvt: &[usize],
diag: &DVector<f64>,
wa2: &DVector<f64>,
dxnorm: f64,
fp: f64,
delta: f64,
wa1: &mut DVector<f64>,
) -> Result<f64, &'static str> {
let n = r.ncols();
for (j, &pivot_idx) in ipvt.iter().enumerate() {
if pivot_idx > 0 && pivot_idx <= n {
wa1[j] = diag[pivot_idx - 1] * (wa2[pivot_idx - 1] / dxnorm);
}
}
for j in 0..n {
let mut sum = 0.0;
for i in 0..j {
sum += r[(i, j)] * wa1[i];
}
wa1[j] = (wa1[j] - sum) / r[(j, j)];
}
let temp = wa1.norm();
Ok(((fp / delta) / temp) / temp)
}
fn compute_upper_bound(
r: &DMatrix<f64>,
ipvt: &[usize],
diag: &DVector<f64>,
qtb: &DVector<f64>,
delta: f64,
wa1: &mut DVector<f64>,
) -> Result<f64, &'static str> {
let n = r.ncols();
for j in 0..n {
let mut sum = 0.0;
for i in 0..=j {
sum += r[(i, j)] * qtb[i];
}
if let Some(&pivot_idx) = ipvt.get(j) {
if pivot_idx > 0 && pivot_idx <= n {
wa1[j] = sum / diag[pivot_idx - 1];
}
}
}
let gnorm = wa1.norm();
let mut paru = gnorm / delta;
if paru == 0.0 {
paru = f64::EPSILON / delta.min(0.1);
}
Ok(paru)
}
fn compute_newton_correction(
r: &DMatrix<f64>,
ipvt: &[usize],
diag: &DVector<f64>,
sdiag: &DVector<f64>,
wa2: &DVector<f64>,
dxnorm: f64,
fp: f64,
delta: f64,
wa1: &mut DVector<f64>,
) -> Result<f64, &'static str> {
let n = r.ncols();
for (j, &pivot_idx) in ipvt.iter().enumerate() {
if pivot_idx > 0 && pivot_idx <= n {
wa1[j] = diag[pivot_idx - 1] * (wa2[pivot_idx - 1] / dxnorm);
}
}
for j in 0..n {
wa1[j] /= sdiag[j];
let temp = wa1[j];
for i in (j + 1)..n {
wa1[i] -= r[(i, j)] * temp;
}
}
let temp = wa1.norm();
Ok(((fp / delta) / temp) / temp)
}
fn qrsolv(
r: &mut DMatrix<f64>,
ipvt: &[usize],
diag_aug: &DVector<f64>,
qtb: &DVector<f64>,
x: &mut DVector<f64>,
sdiag: &mut DVector<f64>,
wa: &mut DVector<f64>,
) -> Result<(), &'static str> {
let n = r.ncols();
for i in 0..n {
sdiag[i] = r[(i, i)];
}
wa.copy_from(qtb);
for k in 0..n {
let j = n - 1 - k;
let mut sum = wa[j];
for i in (j + 1)..n {
sum -= r[(j, i)] * x[i];
}
let reg_diag = (r[(j, j)].powi(2) + diag_aug[j].powi(2)).sqrt();
x[j] = sum / reg_diag;
sdiag[j] = reg_diag;
}
let mut temp_x = x.clone();
for (j, &pivot_idx) in ipvt.iter().enumerate() {
if pivot_idx > 0 && pivot_idx <= n {
temp_x[pivot_idx - 1] = x[j];
}
}
x.copy_from(&temp_x);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::{dmatrix, dvector};
#[test]
fn test_lmpar_basic() {
let mut r = dmatrix![
2.0, 1.0, 0.0;
0.0, 1.5, 0.5;
0.0, 0.0, 1.0
];
let ipvt = vec![1, 2, 3]; let diag = dvector![1.0, 1.0, 1.0];
let qtb = dvector![1.0, 0.5, 0.2];
let delta = 1.0;
let mut par = 0.0;
let result = lmpar(&mut r, &ipvt, &diag, &qtb, delta, &mut par);
assert!(result.is_ok());
let (x, _sdiag) = result.unwrap();
assert_eq!(x.len(), 3);
assert!(par >= 0.0);
}
}