use ndarray::{s, Array, Array1, Array2, ArrayBase, Axis, Data, DataMut, Dimension, Ix0, Ix1, Ix2};
use crate::error::*;
use crate::lapack::least_squares::*;
use crate::layout::*;
use crate::types::*;
pub struct LeastSquaresResult<E: Scalar, I: Dimension> {
pub singular_values: Array1<E::Real>,
pub solution: Array<E, I>,
pub rank: i32,
pub residual_sum_of_squares: Option<Array<E::Real, I::Smaller>>,
}
pub trait LeastSquaresSvd<D, E, I>
where
D: Data<Elem = E>,
E: Scalar + Lapack,
I: Dimension,
{
fn least_squares(&self, rhs: &ArrayBase<D, I>) -> Result<LeastSquaresResult<E, I>>;
}
pub trait LeastSquaresSvdInto<D, E, I>
where
D: Data<Elem = E>,
E: Scalar + Lapack,
I: Dimension,
{
fn least_squares_into(self, rhs: ArrayBase<D, I>) -> Result<LeastSquaresResult<E, I>>;
}
pub trait LeastSquaresSvdInPlace<D, E, I>
where
D: Data<Elem = E>,
E: Scalar + Lapack,
I: Dimension,
{
fn least_squares_in_place(
&mut self,
rhs: &mut ArrayBase<D, I>,
) -> Result<LeastSquaresResult<E, I>>;
}
impl<E, D> LeastSquaresSvd<D, E, Ix1> for ArrayBase<D, Ix2>
where
E: Scalar + Lapack + LeastSquaresSvdDivideConquer_,
D: Data<Elem = E>,
{
fn least_squares(&self, rhs: &ArrayBase<D, Ix1>) -> Result<LeastSquaresResult<E, Ix1>> {
let a = self.to_owned();
let b = rhs.to_owned();
a.least_squares_into(b)
}
}
impl<E, D> LeastSquaresSvd<D, E, Ix2> for ArrayBase<D, Ix2>
where
E: Scalar + Lapack + LeastSquaresSvdDivideConquer_,
D: Data<Elem = E>,
{
fn least_squares(&self, rhs: &ArrayBase<D, Ix2>) -> Result<LeastSquaresResult<E, Ix2>> {
let a = self.to_owned();
let b = rhs.to_owned();
a.least_squares_into(b)
}
}
impl<E, D> LeastSquaresSvdInto<D, E, Ix1> for ArrayBase<D, Ix2>
where
E: Scalar + Lapack + LeastSquaresSvdDivideConquer_,
D: DataMut<Elem = E>,
{
fn least_squares_into(
mut self,
mut rhs: ArrayBase<D, Ix1>,
) -> Result<LeastSquaresResult<E, Ix1>> {
self.least_squares_in_place(&mut rhs)
}
}
impl<E, D> LeastSquaresSvdInto<D, E, Ix2> for ArrayBase<D, Ix2>
where
E: Scalar + Lapack + LeastSquaresSvdDivideConquer_,
D: DataMut<Elem = E>,
{
fn least_squares_into(
mut self,
mut rhs: ArrayBase<D, Ix2>,
) -> Result<LeastSquaresResult<E, Ix2>> {
self.least_squares_in_place(&mut rhs)
}
}
impl<E, D> LeastSquaresSvdInPlace<D, E, Ix1> for ArrayBase<D, Ix2>
where
E: Scalar + Lapack + LeastSquaresSvdDivideConquer_,
D: DataMut<Elem = E>,
{
fn least_squares_in_place(
&mut self,
rhs: &mut ArrayBase<D, Ix1>,
) -> Result<LeastSquaresResult<E, Ix1>> {
let (m, n) = (self.shape()[0], self.shape()[1]);
if n > m {
let mut new_rhs = Array1::<E>::zeros((n,));
new_rhs.slice_mut(s![0..m]).assign(rhs);
compute_least_squares_srhs(self, &mut new_rhs)
} else {
compute_least_squares_srhs(self, rhs)
}
}
}
fn compute_least_squares_srhs<E, D1, D2>(
a: &mut ArrayBase<D1, Ix2>,
rhs: &mut ArrayBase<D2, Ix1>,
) -> Result<LeastSquaresResult<E, Ix1>>
where
E: Scalar + Lapack + LeastSquaresSvdDivideConquer_,
D1: DataMut<Elem = E>,
D2: DataMut<Elem = E>,
{
let LeastSquaresOutput::<E> {
singular_values,
rank,
} = unsafe {
<E as LeastSquaresSvdDivideConquer_>::least_squares(
a.layout()?,
a.as_allocated_mut()?,
rhs.as_slice_memory_order_mut()
.ok_or_else(|| LinalgError::MemoryNotCont)?,
)?
};
let (m, n) = (a.shape()[0], a.shape()[1]);
let solution = rhs.slice(s![0..n]).to_owned();
let residual_sum_of_squares = compute_residual_scalar(m, n, rank, &rhs);
Ok(LeastSquaresResult {
solution,
singular_values: Array::from_shape_vec((singular_values.len(),), singular_values)?,
rank,
residual_sum_of_squares,
})
}
fn compute_residual_scalar<E: Scalar, D: Data<Elem = E>>(
m: usize,
n: usize,
rank: i32,
b: &ArrayBase<D, Ix1>,
) -> Option<Array<E::Real, Ix0>> {
if m < n || n != rank as usize {
return None;
}
let mut arr: Array<E::Real, Ix0> = Array::zeros(());
arr[()] = b.slice(s![n..]).mapv(|x| x.powi(2).abs()).sum();
Some(arr)
}
impl<E, D> LeastSquaresSvdInPlace<D, E, Ix2> for ArrayBase<D, Ix2>
where
E: Scalar + Lapack + LeastSquaresSvdDivideConquer_,
D: DataMut<Elem = E>,
{
fn least_squares_in_place(
&mut self,
rhs: &mut ArrayBase<D, Ix2>,
) -> Result<LeastSquaresResult<E, Ix2>> {
let (m, n) = (self.shape()[0], self.shape()[1]);
if n > m {
let k = rhs.shape()[1];
let mut new_rhs = Array2::<E>::zeros((n, k));
new_rhs.slice_mut(s![0..m, ..]).assign(rhs);
compute_least_squares_nrhs(self, &mut new_rhs)
} else {
compute_least_squares_nrhs(self, rhs)
}
}
}
fn compute_least_squares_nrhs<E, D1, D2>(
a: &mut ArrayBase<D1, Ix2>,
rhs: &mut ArrayBase<D2, Ix2>,
) -> Result<LeastSquaresResult<E, Ix2>>
where
E: Scalar + Lapack + LeastSquaresSvdDivideConquer_,
D1: DataMut<Elem = E>,
D2: DataMut<Elem = E>,
{
let a_layout = a.layout()?;
let rhs_layout = rhs.layout()?;
let LeastSquaresOutput::<E> {
singular_values,
rank,
} = unsafe {
<E as LeastSquaresSvdDivideConquer_>::least_squares_nrhs(
a_layout,
a.as_allocated_mut()?,
rhs_layout,
rhs.as_allocated_mut()?,
)?
};
let solution: Array2<E> = rhs.slice(s![..a.shape()[1], ..]).to_owned();
let singular_values = Array::from_shape_vec((singular_values.len(),), singular_values)?;
let (m, n) = (a.shape()[0], a.shape()[1]);
let residual_sum_of_squares = compute_residual_array1(m, n, rank, &rhs);
Ok(LeastSquaresResult {
solution,
singular_values,
rank,
residual_sum_of_squares,
})
}
fn compute_residual_array1<E: Scalar, D: Data<Elem = E>>(
m: usize,
n: usize,
rank: i32,
b: &ArrayBase<D, Ix2>,
) -> Option<Array1<E::Real>> {
if m < n || n != rank as usize {
return None;
}
Some(
b.slice(s![n.., ..])
.mapv(|x| x.powi(2).abs())
.sum_axis(Axis(0)),
)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::AbsDiffEq;
use ndarray::{ArcArray1, ArcArray2, Array1, Array2, CowArray};
use num_complex::Complex;
#[test]
fn scipy_test_simple_exact() {
let a = array![[1., 20.], [-30., 4.]];
let bs = vec![
array![[1., 0.], [0., 1.]],
array![[1.], [0.]],
array![[2., 1.], [-30., 4.]],
];
for b in &bs {
let res = a.least_squares(b).unwrap();
assert_eq!(res.rank, 2);
let b_hat = a.dot(&res.solution);
let rssq = (b - &b_hat).mapv(|x| x.powi(2)).sum_axis(Axis(0));
assert!(res
.residual_sum_of_squares
.unwrap()
.abs_diff_eq(&rssq, 1e-12));
assert!(b_hat.abs_diff_eq(&b, 1e-12));
}
}
#[test]
fn scipy_test_simple_overdetermined() {
let a: Array2<f64> = array![[1., 2.], [4., 5.], [3., 4.]];
let b: Array1<f64> = array![1., 2., 3.];
let res = a.least_squares(&b).unwrap();
assert_eq!(res.rank, 2);
let b_hat = a.dot(&res.solution);
let rssq = (&b - &b_hat).mapv(|x| x.powi(2)).sum();
assert!(res.residual_sum_of_squares.unwrap()[()].abs_diff_eq(&rssq, 1e-12));
assert!(res
.solution
.abs_diff_eq(&array![-0.428571428571429, 0.85714285714285], 1e-12));
}
#[test]
fn scipy_test_simple_overdetermined_f32() {
let a: Array2<f32> = array![[1., 2.], [4., 5.], [3., 4.]];
let b: Array1<f32> = array![1., 2., 3.];
let res = a.least_squares(&b).unwrap();
assert_eq!(res.rank, 2);
let b_hat = a.dot(&res.solution);
let rssq = (&b - &b_hat).mapv(|x| x.powi(2)).sum();
assert!(res.residual_sum_of_squares.unwrap()[()].abs_diff_eq(&rssq, 1e-6));
assert!(res
.solution
.abs_diff_eq(&array![-0.428571428571429, 0.85714285714285], 1e-6));
}
fn c(re: f64, im: f64) -> Complex<f64> {
Complex::new(re, im)
}
#[test]
fn scipy_test_simple_overdetermined_complex() {
let a: Array2<c64> = array![
[c(1., 2.), c(2., 0.)],
[c(4., 0.), c(5., 0.)],
[c(3., 0.), c(4., 0.)]
];
let b: Array1<c64> = array![c(1., 0.), c(2., 4.), c(3., 0.)];
let res = a.least_squares(&b).unwrap();
assert_eq!(res.rank, 2);
let b_hat = a.dot(&res.solution);
let rssq = (&b_hat - &b).mapv(|x| x.powi(2).abs()).sum();
assert!(res.residual_sum_of_squares.unwrap()[()].abs_diff_eq(&rssq, 1e-12));
assert!(res.solution.abs_diff_eq(
&array![
c(-0.4831460674157303, 0.258426966292135),
c(0.921348314606741, 0.292134831460674)
],
1e-12
));
}
#[test]
fn scipy_test_simple_underdetermined() {
let a: Array2<f64> = array![[1., 2., 3.], [4., 5., 6.]];
let b: Array1<f64> = array![1., 2.];
let res = a.least_squares(&b).unwrap();
assert_eq!(res.rank, 2);
assert!(res.residual_sum_of_squares.is_none());
let expected = array![-0.055555555555555, 0.111111111111111, 0.277777777777777];
assert!(res.solution.abs_diff_eq(&expected, 1e-12));
}
#[test]
fn scipy_test_simple_underdetermined_nrhs() {
let a: Array2<f64> = array![[1., 2., 3.], [4., 5., 6.]];
let b: Array2<f64> = array![[1., 1.], [2., 2.]];
let res = a.least_squares(&b).unwrap();
assert_eq!(res.rank, 2);
assert!(res.residual_sum_of_squares.is_none());
let expected = array![
[-0.055555555555555, -0.055555555555555],
[0.111111111111111, 0.111111111111111],
[0.277777777777777, 0.277777777777777]
];
assert!(res.solution.abs_diff_eq(&expected, 1e-12));
}
fn assert_result<D: Data<Elem = f64>>(
a: &ArrayBase<D, Ix2>,
b: &ArrayBase<D, Ix1>,
res: &LeastSquaresResult<f64, Ix1>,
) {
assert_eq!(res.rank, 2);
let b_hat = a.dot(&res.solution);
let rssq = (b - &b_hat).mapv(|x| x.powi(2)).sum();
assert!(res.residual_sum_of_squares.as_ref().unwrap()[()].abs_diff_eq(&rssq, 1e-12));
assert!(res
.solution
.abs_diff_eq(&array![-0.428571428571429, 0.85714285714285], 1e-12));
}
#[test]
fn test_least_squares_on_arc() {
let a: ArcArray2<f64> = array![[1., 2.], [4., 5.], [3., 4.]].into_shared();
let b: ArcArray1<f64> = array![1., 2., 3.].into_shared();
let res = a.least_squares(&b).unwrap();
assert_result(&a, &b, &res);
}
#[test]
fn test_least_squares_on_cow() {
let a = CowArray::from(array![[1., 2.], [4., 5.], [3., 4.]]);
let b = CowArray::from(array![1., 2., 3.]);
let res = a.least_squares(&b).unwrap();
assert_result(&a, &b, &res);
}
#[test]
fn test_least_squares_on_view() {
let a: Array2<f64> = array![[1., 2.], [4., 5.], [3., 4.]];
let b: Array1<f64> = array![1., 2., 3.];
let av = a.view();
let bv = b.view();
let res = av.least_squares(&bv).unwrap();
assert_result(&av, &bv, &res);
}
#[test]
fn test_least_squares_on_view_mut() {
let mut a: Array2<f64> = array![[1., 2.], [4., 5.], [3., 4.]];
let mut b: Array1<f64> = array![1., 2., 3.];
let av = a.view_mut();
let bv = b.view_mut();
let res = av.least_squares(&bv).unwrap();
assert_result(&av, &bv, &res);
}
#[test]
fn test_least_squares_into_on_owned() {
let a: Array2<f64> = array![[1., 2.], [4., 5.], [3., 4.]];
let b: Array1<f64> = array![1., 2., 3.];
let ac = a.clone();
let bc = b.clone();
let res = ac.least_squares_into(bc).unwrap();
assert_result(&a, &b, &res);
}
#[test]
fn test_least_squares_into_on_arc() {
let a: ArcArray2<f64> = array![[1., 2.], [4., 5.], [3., 4.]].into_shared();
let b: ArcArray1<f64> = array![1., 2., 3.].into_shared();
let a2 = a.clone();
let b2 = b.clone();
let res = a2.least_squares_into(b2).unwrap();
assert_result(&a, &b, &res);
}
#[test]
fn test_least_squares_into_on_cow() {
let a = CowArray::from(array![[1., 2.], [4., 5.], [3., 4.]]);
let b = CowArray::from(array![1., 2., 3.]);
let a2 = a.clone();
let b2 = b.clone();
let res = a2.least_squares_into(b2).unwrap();
assert_result(&a, &b, &res);
}
#[test]
fn test_least_squares_in_place_on_owned() {
let a = array![[1., 2.], [4., 5.], [3., 4.]];
let b = array![1., 2., 3.];
let mut a2 = a.clone();
let mut b2 = b.clone();
let res = a2.least_squares_in_place(&mut b2).unwrap();
assert_result(&a, &b, &res);
}
#[test]
fn test_least_squares_in_place_on_cow() {
let a = CowArray::from(array![[1., 2.], [4., 5.], [3., 4.]]);
let b = CowArray::from(array![1., 2., 3.]);
let mut a2 = a.clone();
let mut b2 = b.clone();
let res = a2.least_squares_in_place(&mut b2).unwrap();
assert_result(&a, &b, &res);
}
#[test]
fn test_least_squares_in_place_on_mut_view() {
let a = array![[1., 2.], [4., 5.], [3., 4.]];
let b = array![1., 2., 3.];
let mut a2 = a.clone();
let mut b2 = b.clone();
let av = &mut a2.view_mut();
let bv = &mut b2.view_mut();
let res = av.least_squares_in_place(bv).unwrap();
assert_result(&a, &b, &res);
}
#[test]
fn netlib_lapack_example_for_dgels_1() {
let a: Array2<f64> = array![
[1., 1., 1.],
[2., 3., 4.],
[3., 5., 2.],
[4., 2., 5.],
[5., 4., 3.]
];
let b: Array1<f64> = array![-10., 12., 14., 16., 18.];
let expected: Array1<f64> = array![2., 1., 1.];
let result = a.least_squares(&b).unwrap();
assert!(result.solution.abs_diff_eq(&expected, 1e-12));
let residual = b - a.dot(&result.solution);
let resid_ssq = result.residual_sum_of_squares.unwrap();
assert!((resid_ssq[()] - residual.dot(&residual)).abs() < 1e-12);
}
#[test]
fn netlib_lapack_example_for_dgels_2() {
let a: Array2<f64> = array![
[1., 1., 1.],
[2., 3., 4.],
[3., 5., 2.],
[4., 2., 5.],
[5., 4., 3.]
];
let b: Array1<f64> = array![-3., 14., 12., 16., 16.];
let expected: Array1<f64> = array![1., 1., 2.];
let result = a.least_squares(&b).unwrap();
assert!(result.solution.abs_diff_eq(&expected, 1e-12));
let residual = b - a.dot(&result.solution);
let resid_ssq = result.residual_sum_of_squares.unwrap();
assert!((resid_ssq[()] - residual.dot(&residual)).abs() < 1e-12);
}
#[test]
fn netlib_lapack_example_for_dgels_nrhs() {
let a: Array2<f64> = array![
[1., 1., 1.],
[2., 3., 4.],
[3., 5., 2.],
[4., 2., 5.],
[5., 4., 3.]
];
let b: Array2<f64> = array![[-10., -3.], [12., 14.], [14., 12.], [16., 16.], [18., 16.]];
let expected: Array2<f64> = array![[2., 1.], [1., 1.], [1., 2.]];
let result = a.least_squares(&b).unwrap();
assert!(result.solution.abs_diff_eq(&expected, 1e-12));
let residual = &b - &a.dot(&result.solution);
let residual_ssq = residual.mapv(|x| x.powi(2)).sum_axis(Axis(0));
assert!(result
.residual_sum_of_squares
.unwrap()
.abs_diff_eq(&residual_ssq, 1e-12));
}
use crate::layout::MatrixLayout;
use ndarray::ErrorKind;
#[test]
fn test_incompatible_shape_error_on_mismatching_num_rows() {
let a: Array2<f64> = array![[1., 2.], [4., 5.], [3., 4.]];
let b: Array1<f64> = array![1., 2.];
let res = a.least_squares(&b);
match res {
Err(err) => match err {
LinalgError::Shape(shape_error) => {
assert_eq!(shape_error.kind(), ErrorKind::IncompatibleShape)
}
_ => panic!("Expected ShapeError"),
},
_ => panic!("Expected Err()"),
}
}
#[test]
fn test_incompatible_shape_error_on_mismatching_layout() {
let a: Array2<f64> = array![[1., 2.], [4., 5.], [3., 4.]];
let b = array![[1.], [2.]].t().to_owned();
assert_eq!(b.layout().unwrap(), MatrixLayout::F((2, 1)));
let res = a.least_squares(&b);
match res {
Err(err) => match err {
LinalgError::Shape(shape_error) => {
assert_eq!(shape_error.kind(), ErrorKind::IncompatibleShape)
}
_ => panic!("Expected ShapeError"),
},
_ => panic!("Expected Err()"),
}
}
}