use nalgebra_sparse::csc::CscMatrix;
use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng};
use std::mem;
extern crate ndarray;
use ndarray::prelude::*;
pub mod error;
use error::SvdLibError;
pub struct SvdRec {
pub d: usize,
pub ut: Array2<f64>,
pub s: Array1<f64>,
pub vt: Array2<f64>,
pub diagnostics: Diagnostics,
}
pub struct Diagnostics {
pub non_zero: usize,
pub dimensions: usize,
pub transposed: bool,
pub lanczos_steps: usize,
pub ritz_values_stabilized: usize,
pub significant_values: usize,
pub singular_values: usize,
}
#[allow(clippy::redundant_field_names)]
#[allow(non_snake_case)]
#[track_caller]
pub fn svdLAS2(
csc: &CscMatrix<f64>,
dim: usize,
end: &[f64; 2],
kappa: f64,
random_seed: u32,
) -> Result<SvdRec, SvdLibError> {
let iterations = csc.nrows().min(csc.ncols());
let dimensions = match dim.min(iterations) {
n if n > 0 => n,
_ => iterations,
};
if dimensions < 2 {
return Err(SvdLibError::Las2Error(format!(
"svdLAS2: insufficient dimensions: {}",
dimensions
)));
}
let transpose = csc.ncols() as f64 >= (csc.nrows() as f64 * 1.2);
let tm: CscMatrix<f64>;
let A = match transpose {
true => {
tm = csc.transpose();
&tm
}
false => csc,
};
let mut wrk = WorkSpace::new(A.ncols(), iterations)?;
let mut store = Store::new(A.ncols())?;
let mut neig = 0;
let steps = lanso(
A,
dimensions,
iterations,
end,
&mut wrk,
&mut neig,
&mut store,
random_seed,
)?;
let kappa = kappa.abs().max(eps34());
let mut R = ritvec(A, dimensions, kappa, &mut wrk, steps, neig, &mut store)?;
if transpose {
mem::swap(&mut R.Ut, &mut R.Vt);
}
Ok(SvdRec {
d: R.d,
ut: Array::from_shape_vec((R.Ut.rows, R.Ut.cols), R.Ut.value)?,
s: Array::from_shape_vec(R.S.len(), R.S)?,
vt: Array::from_shape_vec((R.Vt.rows, R.Vt.cols), R.Vt.value)?,
diagnostics: Diagnostics {
non_zero: A.nnz(),
dimensions: dimensions,
transposed: transpose,
lanczos_steps: steps + 1,
ritz_values_stabilized: neig,
significant_values: R.d,
singular_values: R.nsig,
},
})
}
const MAXLL: usize = 2;
fn eps34() -> f64 {
f64::EPSILON.powf(0.75) }
struct Store {
n: usize,
vecs: Vec<Vec<f64>>,
}
impl Store {
fn new(n: usize) -> Result<Self, SvdLibError> {
Ok(Self { n, vecs: vec![] })
}
fn storq(&mut self, idx: usize, v: &[f64]) {
while idx + MAXLL >= self.vecs.len() {
self.vecs.push(vec![0.0; self.n]);
}
self.vecs[idx + MAXLL].copy_from_slice(v);
}
fn storp(&mut self, idx: usize, v: &[f64]) {
while idx >= self.vecs.len() {
self.vecs.push(vec![0.0; self.n]);
}
self.vecs[idx].copy_from_slice(v);
}
fn retrq(&mut self, idx: usize) -> &[f64] {
&self.vecs[idx + MAXLL]
}
fn retrp(&mut self, idx: usize) -> &[f64] {
&self.vecs[idx]
}
}
struct WorkSpace {
w0: Vec<f64>, w1: Vec<f64>, w2: Vec<f64>, w3: Vec<f64>, w4: Vec<f64>, w5: Vec<f64>, alf: Vec<f64>, eta: Vec<f64>, oldeta: Vec<f64>, bet: Vec<f64>, bnd: Vec<f64>, ritz: Vec<f64>, }
impl WorkSpace {
fn new(n: usize, m: usize) -> Result<Self, SvdLibError> {
Ok(Self {
w0: vec![0.0; n],
w1: vec![0.0; n],
w2: vec![0.0; n],
w3: vec![0.0; n],
w4: vec![0.0; n],
w5: vec![0.0; n],
alf: vec![0.0; m],
eta: vec![0.0; m],
oldeta: vec![0.0; m],
bet: vec![0.0; 1 + m],
ritz: vec![0.0; 1 + m],
bnd: vec![f64::MAX; 1 + m],
})
}
}
#[allow(non_snake_case)]
#[derive(Debug)]
struct DMat {
rows: usize,
cols: usize,
value: Vec<f64>,
}
#[allow(non_snake_case)]
#[derive(Debug)]
struct SVDRawRec {
d: usize,
nsig: usize,
Ut: DMat,
S: Vec<f64>,
Vt: DMat,
}
#[track_caller]
fn compare(computed: f64, expected: f64) -> bool {
(expected - computed).abs() < f64::EPSILON
}
#[track_caller]
fn insert_sort(n: usize, array1: &mut [f64], array2: &mut [f64]) {
for i in 1..n {
let t1 = array1[i];
let t2 = array2[i];
let mut j: i32 = i as i32 - 1;
while j >= 0 && t1 < array1[j as usize] {
array1[(j + 1) as usize] = array1[j as usize];
array2[(j + 1) as usize] = array2[j as usize];
j -= 1;
}
array1[(j + 1) as usize] = t1;
array2[(j + 1) as usize] = t2;
}
}
#[allow(non_snake_case)]
#[track_caller]
fn svd_opa(A: &CscMatrix<f64>, x: &[f64], y: &mut [f64]) {
assert_eq!(
x.len(),
A.ncols(),
"svd_opa: x must be A.ncols() in length, x = {}, A.ncols = {}",
x.len(),
A.ncols()
);
assert_eq!(
y.len(),
A.nrows(),
"svd_opa: y must be A.nrows() in length, y = {}, A.nrows = {}",
y.len(),
A.nrows()
);
let (col_offsets, row_indices, values) = A.csc_data();
y.fill(0.0);
for (i, xval) in x.iter().enumerate() {
for j in col_offsets[i]..col_offsets[i + 1] {
y[row_indices[j]] += values[j] * xval;
}
}
}
#[allow(non_snake_case)]
#[track_caller]
fn svd_opb(A: &CscMatrix<f64>, x: &[f64], y: &mut [f64]) {
assert_eq!(
x.len(),
A.ncols(),
"svd_opb: x must be A.ncols() in length, x = {}, A.ncols = {}",
x.len(),
A.ncols()
);
assert_eq!(
y.len(),
A.ncols(),
"svd_opb: y must be A.ncols() in length, y = {}, A.ncols = {}",
y.len(),
A.ncols()
);
let (col_offsets, row_indices, values) = A.csc_data();
let mut atx = vec![0.0; A.nrows()];
svd_opa(A, x, &mut atx);
y.fill(0.0);
for (i, yval) in y.iter_mut().enumerate() {
for j in col_offsets[i]..col_offsets[i + 1] {
*yval += values[j] * atx[row_indices[j]];
}
}
}
#[track_caller]
fn svd_daxpy(da: f64, x: &[f64], y: &mut [f64]) {
for (xval, yval) in x.iter().zip(y.iter_mut()) {
*yval += da * xval
}
}
#[track_caller]
fn svd_idamax(n: usize, x: &[f64]) -> usize {
match n {
1 => 0,
_ => {
let mut imax: usize = 0;
let mut dmax = x[imax].abs();
for (i, xval) in x.iter().enumerate().take(n).skip(1) {
if xval.abs() > dmax {
dmax = xval.abs();
imax = i;
}
}
imax
}
}
}
#[track_caller]
fn svd_fsign(a: f64, b: f64) -> f64 {
match a >= 0.0 && b >= 0.0 || a < 0.0 && b < 0.0 {
true => a,
false => -a,
}
}
#[allow(clippy::many_single_char_names)]
#[track_caller]
fn svd_pythag(a: f64, b: f64) -> f64 {
match a.abs().max(b.abs()) {
n if n > 0.0 => {
let mut p = n;
let mut r = (a.abs().min(b.abs()) / p).powi(2);
let mut t = 4.0 + r;
while !compare(t, 4.0) {
let s = r / t;
let u = 1.0 + 2.0 * s;
p *= u;
r *= (s / u).powi(2);
t = 4.0 + r;
}
p
}
_ => 0.0,
}
}
#[track_caller]
fn svd_ddot(x: &[f64], y: &[f64]) -> f64 {
x.iter().zip(y).map(|(a, b)| a * b).sum()
}
#[track_caller]
fn svd_norm(x: &[f64]) -> f64 {
svd_ddot(x, x).sqrt()
}
#[track_caller]
fn svd_datx(da: f64, x: &[f64], y: &mut [f64]) {
for (xval, yval) in x.iter().zip(y.iter_mut()) {
*yval = da * xval
}
}
#[track_caller]
fn svd_dscal(da: f64, x: &mut [f64]) {
for item in x.iter_mut() {
*item *= da;
}
}
#[track_caller]
fn svd_dcopy(n: usize, offset: usize, x: &[f64], y: &mut [f64]) {
assert!(n > 0, "svd_dcopy: unexpected inputs!");
let start = n - 1;
for i in 0..n {
y[offset + start - i] = x[offset + i];
}
}
#[track_caller]
#[allow(clippy::many_single_char_names)]
fn imtqlb(n: usize, d: &mut [f64], e: &mut [f64], bnd: &mut [f64]) -> Result<(), SvdLibError> {
if n == 1 {
return Ok(());
}
bnd[0] = 1.0;
let last = n - 1;
for i in 1..=last {
bnd[i] = 0.0;
e[i - 1] = e[i];
}
e[last] = 0.0;
let mut i: i32 = 0;
for l in 0..=last {
let mut iteration = 0;
while iteration <= 30 {
let mut m = l;
while m < n {
if m == last {
break;
}
let test = d[m].abs() + d[m + 1].abs();
if compare(test, test + e[m].abs()) {
break; }
m += 1;
}
let mut p = d[l];
let mut f = bnd[l];
if m == l {
let mut exchange = true;
if l != 0 {
i = l as i32;
while i >= 1 && exchange {
if p < d[(i - 1) as usize] {
d[i as usize] = d[(i - 1) as usize];
bnd[i as usize] = bnd[(i - 1) as usize];
i -= 1;
} else {
exchange = false;
}
}
}
if exchange {
i = 0;
}
d[i as usize] = p;
bnd[i as usize] = f;
iteration = 31;
} else {
if iteration == 30 {
return Err(SvdLibError::ImtqlbError(
"imtqlb no convergence to an eigenvalue after 30 iterations".to_string(),
));
}
iteration += 1;
let mut g = (d[l + 1] - p) / (2.0 * e[l]);
let mut r = svd_pythag(g, 1.0);
g = d[m] - p + e[l] / (g + svd_fsign(r, g));
let mut s = 1.0;
let mut c = 1.0;
p = 0.0;
let mut underflow = false;
i = m as i32 - 1;
while !underflow && i >= (l as i32) {
f = s * e[i as usize];
let b = c * e[i as usize];
r = svd_pythag(f, g);
e[(i + 1) as usize] = r;
if compare(r, 0.0) {
underflow = true;
} else {
s = f / r;
c = g / r;
g = d[(i + 1) as usize] - p;
r = (d[i as usize] - g) * s + 2.0 * c * b;
p = s * r;
d[(i + 1) as usize] = g + p;
g = c * r - b;
f = bnd[(i + 1) as usize];
bnd[(i + 1) as usize] = s * bnd[i as usize] + c * f;
bnd[i as usize] = c * bnd[i as usize] - s * f;
i -= 1;
}
}
if underflow {
d[(i + 1) as usize] -= p;
} else {
d[l] -= p;
e[l] = g;
}
e[m] = 0.0;
}
}
}
Ok(())
}
#[track_caller]
#[allow(non_snake_case)]
fn startv(
A: &CscMatrix<f64>,
wrk: &mut WorkSpace,
step: u32,
store: &mut Store,
random_seed: u32,
) -> Result<f64, SvdLibError> {
let mut rnm2 = svd_ddot(&wrk.w0, &wrk.w0);
for id in 0..3 {
if id > 0 || step > 0 || compare(rnm2, 0.0) {
if random_seed > 0 {
let mut bytes = [0; 32];
for (i, b) in random_seed.to_le_bytes().iter().enumerate() {
bytes[i] = *b;
}
let mut seeded_rng = StdRng::from_seed(bytes);
wrk.w0.fill_with(|| seeded_rng.gen_range(-1.0..1.0));
} else {
wrk.w0.fill_with(|| thread_rng().gen_range(-1.0..1.0));
}
}
wrk.w3.copy_from_slice(&wrk.w0);
svd_opb(A, &wrk.w3, &mut wrk.w0);
wrk.w3.copy_from_slice(&wrk.w0);
rnm2 = svd_ddot(&wrk.w3, &wrk.w3);
if rnm2 > 0.0 {
break;
}
}
if rnm2 <= 0.0 {
return Err(SvdLibError::StartvError(format!(
"rnm2 <= 0.0, rnm2 = {}",
rnm2
)));
}
if step > 0 {
for i in 0..step as usize {
let v = store.retrq(i);
svd_daxpy(-svd_ddot(&wrk.w3, v), v, &mut wrk.w0);
}
svd_daxpy(-svd_ddot(&wrk.w4, &wrk.w0), &wrk.w2, &mut wrk.w0);
wrk.w3.copy_from_slice(&wrk.w0);
rnm2 = match svd_ddot(&wrk.w3, &wrk.w3) {
dot if dot <= f64::EPSILON * rnm2 => 0.0,
dot => dot,
}
}
Ok(rnm2.sqrt())
}
#[track_caller]
#[allow(non_snake_case)]
fn stpone(
A: &CscMatrix<f64>,
wrk: &mut WorkSpace,
store: &mut Store,
random_seed: u32,
) -> Result<(f64, f64), SvdLibError> {
let mut rnm = startv(A, wrk, 0, store, random_seed)?;
if compare(rnm, 0.0) {
return Err(SvdLibError::StponeError("rnm == 0.0".to_string()));
}
svd_datx(rnm.recip(), &wrk.w0, &mut wrk.w1);
svd_dscal(rnm.recip(), &mut wrk.w3);
svd_opb(A, &wrk.w3, &mut wrk.w0);
wrk.alf[0] = svd_ddot(&wrk.w0, &wrk.w3);
svd_daxpy(-wrk.alf[0], &wrk.w1, &mut wrk.w0);
let t = svd_ddot(&wrk.w0, &wrk.w3);
wrk.alf[0] += t;
svd_daxpy(-t, &wrk.w1, &mut wrk.w0);
wrk.w4.copy_from_slice(&wrk.w0);
rnm = svd_norm(&wrk.w4);
let anorm = rnm + wrk.alf[0].abs();
Ok((rnm, f64::EPSILON.sqrt() * anorm))
}
#[track_caller]
#[allow(non_snake_case)]
#[allow(clippy::too_many_arguments)]
fn lanczos_step(
A: &CscMatrix<f64>,
wrk: &mut WorkSpace,
first: usize,
last: usize,
ll: &mut u32,
enough: &mut bool,
rnm: &mut f64,
tol: &mut f64,
store: &mut Store,
) -> Result<usize, SvdLibError> {
let eps1 = f64::EPSILON * (A.ncols() as f64).sqrt();
let mut j = first;
while j < last {
mem::swap(&mut wrk.w1, &mut wrk.w2);
mem::swap(&mut wrk.w3, &mut wrk.w4);
store.storq(j - 1, &wrk.w2);
if j - 1 < MAXLL {
store.storp(j - 1, &wrk.w4);
}
wrk.bet[j] = *rnm;
if compare(*rnm, 0.0) {
*rnm = startv(A, wrk, j as u32, store, 0)?;
if compare(*rnm, 0.0) {
*enough = true;
}
}
if *enough {
mem::swap(&mut wrk.w1, &mut wrk.w2);
break;
}
svd_datx(rnm.recip(), &wrk.w0, &mut wrk.w1);
svd_dscal(rnm.recip(), &mut wrk.w3);
svd_opb(A, &wrk.w3, &mut wrk.w0);
svd_daxpy(-*rnm, &wrk.w2, &mut wrk.w0);
wrk.alf[j] = svd_ddot(&wrk.w0, &wrk.w3);
svd_daxpy(-wrk.alf[j], &wrk.w1, &mut wrk.w0);
if j <= MAXLL && wrk.alf[j - 1].abs() > 4.0 * wrk.alf[j].abs() {
*ll = j as u32;
}
for i in 0..((j as u32 - 1).min(*ll)) as usize {
let v1 = store.retrp(i);
let t = svd_ddot(v1, &wrk.w0);
let v2 = store.retrq(i);
svd_daxpy(-t, v2, &mut wrk.w0);
wrk.eta[i] = eps1;
wrk.oldeta[i] = eps1;
}
let t = svd_ddot(&wrk.w0, &wrk.w4);
svd_daxpy(-t, &wrk.w2, &mut wrk.w0);
if wrk.bet[j] > 0.0 {
wrk.bet[j] += t;
}
let t = svd_ddot(&wrk.w0, &wrk.w3);
svd_daxpy(-t, &wrk.w1, &mut wrk.w0);
wrk.alf[j] += t;
wrk.w4.copy_from_slice(&wrk.w0);
*rnm = svd_norm(&wrk.w4);
let anorm = wrk.bet[j] + wrk.alf[j].abs() + *rnm;
*tol = f64::EPSILON.sqrt() * anorm;
ortbnd(wrk, j, *rnm, eps1);
purge(A.ncols(), *ll, wrk, j, rnm, *tol, store);
if *rnm <= *tol {
*rnm = 0.0;
}
j += 1;
}
Ok(j)
}
#[track_caller]
#[allow(non_snake_case)]
fn purge(
n: usize,
ll: u32,
wrk: &mut WorkSpace,
step: usize,
rnm: &mut f64,
tol: f64,
store: &mut Store,
) {
if step < 2 + ll as usize {
return;
}
let reps = f64::EPSILON.sqrt();
let eps1 = f64::EPSILON * (n as f64).sqrt();
let k = svd_idamax((step as u32 - (ll + 1)) as usize, &wrk.eta) + ll as usize;
if wrk.eta[k].abs() > reps {
let reps1 = eps1 / reps;
let mut iteration = 0;
let mut flag = true;
while iteration < 2 && flag {
if *rnm > tol {
let mut tq = 0.0;
let mut tr = 0.0;
for i in (ll as usize)..step as usize {
let v = store.retrq(i);
let t = svd_ddot(v, &wrk.w3);
tq += t.abs();
svd_daxpy(-t, v, &mut wrk.w1);
let t = svd_ddot(v, &wrk.w4);
tr += t.abs();
svd_daxpy(-t, v, &mut wrk.w0);
}
wrk.w3.copy_from_slice(&wrk.w1);
let t = svd_ddot(&wrk.w0, &wrk.w3);
tr += t.abs();
svd_daxpy(-t, &wrk.w1, &mut wrk.w0);
wrk.w4.copy_from_slice(&wrk.w0);
*rnm = svd_norm(&wrk.w4);
if tq <= reps1 && tr <= *rnm * reps1 {
flag = false;
}
}
iteration += 1;
}
for i in (ll as usize)..=step as usize {
wrk.eta[i] = eps1;
wrk.oldeta[i] = eps1;
}
}
}
#[track_caller]
fn ortbnd(wrk: &mut WorkSpace, step: usize, rnm: f64, eps1: f64) {
if step < 1 {
return;
}
if !compare(rnm, 0.0) && step > 1 {
wrk.oldeta[0] = (wrk.bet[1] * wrk.eta[1] + (wrk.alf[0] - wrk.alf[step]) * wrk.eta[0]
- wrk.bet[step] * wrk.oldeta[0])
/ rnm
+ eps1;
if step > 2 {
for i in 1..=(step as i32 - 2) as usize {
wrk.oldeta[i] = (wrk.bet[i + 1] * wrk.eta[i + 1]
+ (wrk.alf[i] - wrk.alf[step]) * wrk.eta[i]
+ wrk.bet[i] * wrk.eta[i - 1]
- wrk.bet[step] * wrk.oldeta[i])
/ rnm
+ eps1;
}
}
}
wrk.oldeta[step - 1] = eps1;
mem::swap(&mut wrk.oldeta, &mut wrk.eta);
wrk.eta[step] = eps1;
}
#[track_caller]
fn error_bound(
enough: &mut bool,
endl: f64,
endr: f64,
ritz: &mut [f64],
bnd: &mut [f64],
step: usize,
tol: f64,
) -> usize {
let mid = svd_idamax(step + 1, bnd) as i32;
let mut i: i32 = (((step + 1) + (step - 1)) / 2) as i32;
while i > mid + 1 {
if (ritz[(i - 1) as usize] - ritz[i as usize]).abs() < eps34() * ritz[i as usize].abs()
&& bnd[i as usize] > tol
&& bnd[(i - 1) as usize] > tol
{
bnd[(i - 1) as usize] =
(bnd[i as usize].powi(2) + bnd[(i - 1) as usize].powi(2)).sqrt();
bnd[i as usize] = 0.0;
}
i -= 1;
}
let mut i: i32 = (((step + 1) - (step - 1)) / 2) as i32;
while i < mid - 1 {
if (ritz[(i + 1) as usize] - ritz[i as usize]).abs() < eps34() * ritz[i as usize].abs()
&& bnd[i as usize] > tol
&& bnd[(i + 1) as usize] > tol
{
bnd[(i + 1) as usize] =
(bnd[i as usize].powi(2) + bnd[(i + 1) as usize].powi(2)).sqrt();
bnd[i as usize] = 0.0;
}
i += 1;
}
let mut neig = 0;
let mut gapl = ritz[step] - ritz[0];
for i in 0..=step {
let mut gap = gapl;
if i < step {
gapl = ritz[i + 1] - ritz[i];
}
gap = gap.min(gapl);
if gap > bnd[i] {
bnd[i] = bnd[i] * (bnd[i] / gap);
}
if bnd[i] <= 16.0 * f64::EPSILON * ritz[i].abs() {
neig += 1;
if !*enough {
*enough = endl < ritz[i] && ritz[i] < endr;
}
}
}
neig
}
#[track_caller]
#[allow(clippy::many_single_char_names)]
fn imtql2(
nm: usize,
n: usize,
d: &mut [f64],
e: &mut [f64],
z: &mut [f64],
) -> Result<(), SvdLibError> {
if n == 1 {
return Ok(());
}
let last = n - 1;
for i in 1..n {
e[i - 1] = e[i];
}
e[last] = 0.0;
let nnm = n * nm;
for l in 0..n {
let mut iteration = 0;
while iteration <= 30 {
let mut m = l;
while m < n {
if m == last {
break;
}
let test = d[m].abs() + d[m + 1].abs();
if compare(test, test + e[m].abs()) {
break; }
m += 1;
}
if m == l {
break;
}
if iteration == 30 {
return Err(SvdLibError::Imtql2Error(
"imtql2 no convergence to an eigenvalue after 30 iterations".to_string(),
));
}
iteration += 1;
let mut g = (d[l + 1] - d[l]) / (2.0 * e[l]);
let mut r = svd_pythag(g, 1.0);
g = d[m] - d[l] + e[l] / (g + svd_fsign(r, g));
let mut s = 1.0;
let mut c = 1.0;
let mut p = 0.0;
let mut underflow = false;
let mut i: i32 = m as i32 - 1;
while !underflow && i >= (l as i32) {
let mut f = s * e[i as usize];
let b = c * e[i as usize];
r = svd_pythag(f, g);
e[(i + 1) as usize] = r;
if compare(r, 0.0) {
underflow = true;
} else {
s = f / r;
c = g / r;
g = d[(i + 1) as usize] - p;
r = (d[i as usize] - g) * s + 2.0 * c * b;
p = s * r;
d[(i + 1) as usize] = g + p;
g = c * r - b;
for k in (0..nnm).step_by(n) {
let index = k + i as usize;
f = z[index + 1];
z[index + 1] = s * z[index] + c * f;
z[index] = c * z[index] - s * f;
}
i -= 1;
}
}
if underflow {
d[(i + 1) as usize] -= p;
} else {
d[l] -= p;
e[l] = g;
}
e[m] = 0.0;
}
}
for l in 1..n {
let i = l - 1;
let mut k = i;
let mut p = d[i];
for (j, item) in d.iter().enumerate().take(n).skip(l) {
if *item < p {
k = j;
p = *item;
}
}
if k != i {
d[k] = d[i];
d[i] = p;
for j in (0..nnm).step_by(n) {
z.swap(j + i, j + k);
}
}
}
Ok(())
}
#[track_caller]
#[allow(non_snake_case)]
fn rotateArray(a: &mut [f64], x: usize) {
let n = a.len();
let mut j = 0;
let mut start = 0;
let mut t1 = a[0];
for _ in 0..n {
j = match j >= x {
true => j - x,
false => j + n - x,
};
let t2 = a[j];
a[j] = t1;
if j == start {
j += 1;
start = j;
t1 = a[j];
} else {
t1 = t2;
}
}
}
#[track_caller]
#[allow(non_snake_case)]
#[allow(clippy::too_many_arguments)]
fn ritvec(
A: &CscMatrix<f64>,
dimensions: usize,
kappa: f64,
wrk: &mut WorkSpace,
steps: usize,
neig: usize,
store: &mut Store,
) -> Result<SVDRawRec, SvdLibError> {
let js = steps + 1;
let jsq = js * js;
let mut s = vec![0.0; jsq as usize];
for i in (0..jsq).step_by(js + 1) {
s[i] = 1.0;
}
let mut Vt = DMat {
rows: dimensions,
cols: A.ncols(),
value: vec![0.0; A.ncols() * dimensions],
};
svd_dcopy(js, 0, &wrk.alf, &mut Vt.value);
svd_dcopy(steps, 1, &wrk.bet, &mut wrk.w5);
imtql2(js as usize, js as usize, &mut Vt.value, &mut wrk.w5, &mut s)?;
let mut nsig = 0;
let mut x = 0;
let mut id2 = jsq - js;
for k in 0..js as usize {
if wrk.bnd[k] <= kappa * wrk.ritz[k].abs() && k as i32 > (js as i32 - neig as i32 - 1) {
x = match x {
0 => dimensions - 1,
_ => x - 1,
};
let offset = x * Vt.cols;
Vt.value[offset..(offset + Vt.cols)].fill(0.0);
let mut idx = id2 as i32;
for i in 0..js as usize {
if s[idx as usize] != 0.0 {
for (j, item) in store.retrq(i).iter().enumerate().take(Vt.cols as usize) {
Vt.value[j + offset] += s[idx as usize] * item;
}
}
idx -= js as i32;
}
nsig += 1;
}
id2 += 1;
}
if x > 0 {
rotateArray(&mut Vt.value, x * Vt.cols);
}
let mut Ut = DMat {
rows: dimensions,
cols: A.nrows(),
value: vec![0.0; A.nrows() * dimensions],
};
let mut S = vec![0.0; dimensions];
let d = dimensions.min(nsig);
let mut tmp_vec = vec![0.0; Vt.cols as usize];
for (i, sval) in S.iter_mut().enumerate().take(d as usize) {
let vt_offset = i * Vt.cols as usize;
let ut_offset = i * Ut.cols as usize;
let vt_vec = &Vt.value[vt_offset..(vt_offset + Vt.cols as usize)];
let ut_vec = &mut Ut.value[ut_offset..(ut_offset + Ut.cols as usize)];
svd_opb(A, vt_vec, &mut tmp_vec);
let t = svd_ddot(vt_vec, &tmp_vec);
*sval = t.sqrt();
svd_daxpy(-t, vt_vec, &mut tmp_vec);
wrk.bnd[js as usize] = svd_norm(&tmp_vec) * sval.recip();
svd_opa(A, vt_vec, ut_vec);
svd_dscal(sval.recip(), ut_vec);
}
Ok(SVDRawRec {
d,
nsig,
Ut,
S,
Vt,
})
}
#[track_caller]
#[allow(non_snake_case)]
#[allow(clippy::too_many_arguments)]
fn lanso(
A: &CscMatrix<f64>,
dim: usize,
iterations: usize,
end: &[f64; 2],
wrk: &mut WorkSpace,
neig: &mut usize,
store: &mut Store,
random_seed: u32,
) -> Result<usize, SvdLibError> {
let endl = end[0];
let endr = end[1];
let rnm_tol = stpone(A, wrk, store, random_seed)?;
let mut rnm = rnm_tol.0;
let mut tol = rnm_tol.1;
let eps1 = f64::EPSILON * (A.ncols() as f64).sqrt();
wrk.eta[0] = eps1;
wrk.oldeta[0] = eps1;
let mut ll = 0;
let mut first = 1;
let mut last = iterations.min(dim.max(8) + dim);
let mut enough = false;
let mut j = 0;
let mut intro = 0;
while !enough {
if rnm <= tol {
rnm = 0.0;
}
let steps = lanczos_step(
A,
wrk,
first,
last,
&mut ll,
&mut enough,
&mut rnm,
&mut tol,
store,
)?;
j = match enough {
true => steps - 1,
false => last - 1,
};
first = j + 1;
wrk.bet[first] = rnm;
let mut l = 0;
for _ in 0..j {
if l > j {
break;
}
let mut k = j;
for i in l..=j {
k = i;
if compare(wrk.bet[i + 1], 0.0) {
break;
}
k += 1;
}
if k > j {
k = j;
}
let sz = k as i32 - l as i32;
svd_dcopy((sz + 1) as usize, l, &wrk.alf, &mut wrk.ritz);
svd_dcopy(sz as usize, l + 1, &wrk.bet, &mut wrk.w5);
imtqlb(
(sz + 1) as usize,
&mut wrk.ritz[l..],
&mut wrk.w5[l..],
&mut wrk.bnd[l..],
)?;
for m in l..=k {
wrk.bnd[m] = rnm * wrk.bnd[m].abs();
}
l = k + 1;
}
insert_sort(j + 1, &mut wrk.ritz, &mut wrk.bnd);
*neig = error_bound(&mut enough, endl, endr, &mut wrk.ritz, &mut wrk.bnd, j, tol);
if *neig < dim {
if *neig == 0 {
last = first + 9;
intro = first;
} else {
last = first + 3.max(1 + ((j - intro) * (dim - *neig)) / *neig) as usize;
}
last = last.min(iterations);
} else {
enough = true
}
enough = enough || first >= iterations;
}
store.storq(j, &wrk.w1);
Ok(j)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(non_snake_case)]
fn basic_2x2() {
let mut coo = nalgebra_sparse::coo::CooMatrix::<f64>::new(2, 2);
coo.push(0, 0, 4.0);
coo.push(1, 0, 3.0);
coo.push(1, 1, -5.0);
let csc = nalgebra_sparse::csc::CscMatrix::from(&coo);
let svd: SvdRec = svdLAS2(
&csc, 0, &[-1.0e-30, 1.0e-30], 1e-6, 0, )
.unwrap();
println!("svd.d = {}", svd.d);
println!("U = {:#?}", svd.ut.t());
println!("S = {:#?}", svd.s);
println!("V = {:#?}", svd.vt.t());
let M = svd.ut.t().dot(&Array2::from_diag(&svd.s)).dot(&svd.vt);
let epsilon = 1.0e-12;
assert_eq!(svd.d, 2);
assert!((M[[0, 0]] - 4.0).abs() < epsilon);
assert!((M[[0, 1]] - 0.0).abs() < epsilon);
assert!((M[[1, 0]] - 3.0).abs() < epsilon);
assert!((M[[1, 1]] - -5.0).abs() < epsilon);
assert!((svd.s[0] - 6.3245553203368).abs() < epsilon);
assert!((svd.s[1] - 3.1622776601684).abs() < epsilon);
}
}