#[allow(unused)]
use log::Level::{Debug, Trace};
use log::log_enabled;
#[allow(unused)]
use anyhow::anyhow;
use cpu_time::ProcessTime;
use std::time::SystemTime;
use num_traits::cast::FromPrimitive;
use num_traits::float::*;
use lax::Lapack;
use ndarray::{Array1, Array2, ArrayView, ArrayView1, ArrayView2, Dim, Ix1, Ix2, s};
use std::any::TypeId;
use lapacke::{Layout, dggsvd3, sggsvd3};
#[derive(Copy, Clone, Debug)]
pub struct GSvdOptParams {
alpha_1: f64,
transpose_1: bool,
alpha_2: f64,
transpose_2: bool,
}
impl GSvdOptParams {
pub fn new(alpha_1: f64, transpose_1: bool, alpha_2: f64, transpose_2: bool) -> Self {
GSvdOptParams {
alpha_1,
transpose_1,
alpha_2,
transpose_2,
}
}
pub fn get_alpha_1(&self) -> f64 {
self.alpha_1
}
pub fn get_alpha_2(&self) -> f64 {
self.alpha_2
}
pub fn get_transpose_1(&self) -> bool {
self.transpose_1
}
pub fn get_transpose_2(&self) -> bool {
self.transpose_2
}
}
#[cfg_attr(doc, katexit::katexit)]
pub struct GSvd<'a, F: Lapack> {
a: &'a mut Array2<F>,
b: &'a mut Array2<F>,
opt_params: Option<GSvdOptParams>,
}
#[cfg_attr(doc, katexit::katexit)]
pub struct GSvdResult<F: Float> {
m: usize,
n: usize,
p: usize,
k: usize,
l: usize,
pub(crate) v1: Option<Array2<F>>,
pub(crate) v2: Option<Array2<F>>,
pub(crate) alpha: Option<Array1<F>>,
pub(crate) beta: Option<Array1<F>>,
pub(crate) s1: Option<Array1<F>>,
pub(crate) s2: Option<Array1<F>>,
pub(crate) decreasing_s1: Option<Array1<usize>>,
pub(crate) _commonx: Option<Array2<F>>,
}
impl<F> GSvdResult<F>
where
F: Float + Lapack + ndarray::ScalarOperand + sprs::MulAcc,
{
pub(crate) fn new() -> Self {
GSvdResult {
m: 0,
n: 0,
p: 0,
k: 0,
l: 0,
v1: None,
v2: None,
s1: None,
s2: None,
alpha: None,
beta: None,
decreasing_s1: None,
_commonx: None,
}
}
pub fn get_mat1_dim(self) -> (usize, usize) {
(self.m, self.n)
}
pub fn get_mat2_dim(&self) -> (usize, usize) {
(self.p, self.n)
}
pub fn get_k(&self) -> usize {
self.k
}
pub fn get_l(&self) -> usize {
self.l
}
pub fn get_s1(&self) -> Option<ArrayView1<F>> {
if self.m >= self.k + self.l {
log::debug!("atp::gsvd::get_s1 : m-k-l >= 0");
assert!(self.l > 0);
let s1_v = self
.alpha
.as_ref()
.unwrap()
.slice(s![self.k..(self.k + self.l)]);
Some(s1_v)
} else {
log::debug!("atp::gsvd::get_s1 : m-k-l < 0");
assert!(self.m >= self.k);
let s1_v = self.alpha.as_ref().unwrap().slice(s![self.k..(self.m)]);
Some(s1_v)
}
}
pub fn get_s2(&self) -> Option<ArrayView1<F>> {
if self.m >= self.k + self.l {
log::debug!("atp::gsvd::get_s2 : m-k-l >= 0");
assert!(self.l > 0);
let s2_v = self
.beta
.as_ref()
.unwrap()
.slice(s![self.k..(self.k + self.l)]);
Some(s2_v)
} else {
log::debug!("atp::gsvd::get_s2 : m-k-l < 0");
assert!(self.m >= self.k);
let s2_v = self.beta.as_ref().unwrap().slice(s![self.k..(self.m)]);
Some(s2_v)
}
}
pub fn get_alpha(&self) -> Option<&Array1<F>> {
self.alpha.as_ref()
}
pub fn get_beta(&self) -> Option<&Array1<F>> {
self.beta.as_ref()
}
#[allow(unused)]
pub(crate) fn debug_print(&self) {
println!("\n GSvdResult : ");
println!(" k : {}, l : {}", self.k, self.l);
assert!(self.alpha.is_some());
let alpha = self.alpha.as_ref().unwrap();
assert!(self.beta.is_some());
let beta = self.beta.as_ref().unwrap();
println!("\n eigen values alpha beta \n");
for i in 0..alpha.len().min(100) {
println!(
" i : {}, {:.3e} {:.3e} ",
i, alpha[i], beta[i]
);
}
}
pub(crate) fn init_from_lapack(
&mut self,
m: i64,
n: i64,
p: i64,
u: Array2<F>,
v: Array2<F>,
k: i64,
l: i64,
alpha: Array1<F>,
beta: Array1<F>,
permuta: Array1<i32>,
) {
self.v1 = Some(u);
self.v2 = Some(v);
log::debug!(
"\n\n got from init_from_lapack : \n m : {}, n : {}, p : {}, k : {}, l : {} ",
m,
n,
p,
k,
l
);
log::debug!(
"alpha length : {}, beta length : {}",
alpha.len(),
beta.len()
);
assert_eq!(alpha.len(), n as usize);
assert_eq!(beta.len(), n as usize);
assert!(m >= 0);
assert!(l >= 0);
assert!(k >= 0);
self.m = usize::try_from(m).unwrap();
self.n = usize::try_from(n).unwrap();
self.p = usize::try_from(p).unwrap();
self.k = usize::try_from(k).unwrap();
self.l = usize::try_from(l).unwrap();
let s = m.min(k + l) as usize;
if m - k - l >= 0 {
log::debug!("m-k-l >= 0");
assert!(l > 0);
} else {
log::debug!("m-k-l < 0");
assert!(m >= k);
}
let s1_v: ArrayView<F, Dim<[usize; 1]>> = alpha.slice(s![k as usize..s]);
let s2_v: ArrayView<F, Dim<[usize; 1]>> = beta.slice(s![k as usize..s]);
if log_enabled!(log::Level::Trace) {
for i in 0..k as usize {
log::trace!(
" i {}, alpha[i] {:.3e}, beta[i] {:.3e}",
i,
alpha[i],
beta[i]
);
}
for i in 0..s1_v.len() {
log::trace!(" i {}, C[i] {:.3e}, S[i] {:.3e}", i, s1_v[i], s2_v[i]);
}
for i in (k + l).min(m) as usize..n as usize {
log::trace!(
" i {}, alpha[i] {:.3e}, beta[i] {:.3e}",
i,
alpha[i],
beta[i]
);
}
}
let check: Vec<F> = s1_v
.iter()
.zip(s2_v.iter())
.map(|x| *x.0 * *x.0 + *x.1 * *x.1)
.collect();
for v in check {
let epsil = (1. - v.to_f64().unwrap()).abs();
if epsil > 1.0E-5 {
log::error!(" epsil (should be very small < 1.E-5) = {:.3e}", epsil);
}
}
let k_u = k as usize;
let decreasing_alpha: Array1<usize> = (k_u..s)
.map(|i| usize::from_i32(permuta[i]).unwrap() - k_u - 1)
.collect();
log::trace!("permuta : {:?}", permuta);
log::trace!("decreasing_alpha : {:?}", decreasing_alpha);
for i in (k_u + 1)..s {
if s1_v[decreasing_alpha[i]] > s1_v[decreasing_alpha[i - 1]] {
log::error!(
"alpha non decreasing at i : {} {} {}",
i,
s1_v[decreasing_alpha[i]],
s1_v[decreasing_alpha[i - 1]]
);
panic!("non sorted alpha");
}
}
if !decreasing_alpha.is_empty() {
log::debug!(
" greatest alpha < 1. : {:.3e}, smallest alpha > 0. : {:.3e}",
s1_v[decreasing_alpha[0]],
s1_v[decreasing_alpha[decreasing_alpha.len() - 1]]
);
}
self.s1 = Some(s1_v.to_owned());
self.s2 = Some(s2_v.to_owned());
self.alpha = Some(alpha);
self.beta = Some(beta);
if !decreasing_alpha.is_empty() {
self.decreasing_s1 = Some(decreasing_alpha)
}
log::debug!(
"exiting GSvdResult::init_from_lapack m : {}, n : {}, p : {}, k : {}, l : {}",
m,
n,
p,
k,
l
)
}
pub fn get_v1(&self) -> Option<&Array2<F>> {
match &self.v1 {
Some(s) => Some(s),
_ => None,
}
}
pub fn get_v2(&self) -> Option<&Array2<F>> {
match &self.v2 {
Some(s) => Some(s),
_ => None,
}
}
#[allow(unused)]
pub(crate) fn dump_u(&self) {
if self.v1.is_some() {
let u = self.v1.as_ref().unwrap();
log::debug!("\n dumping U");
dump::<F>(&u.view());
}
}
#[allow(unused)]
pub(crate) fn dump_v(&self) {
if self.v1.is_some() {
let v = self.v2.as_ref().unwrap();
log::debug!("\n dumping V");
dump::<F>(&v.view());
}
}
#[allow(unused)]
pub(crate) fn check_uv_orthogonal(&self) -> Result<(), ()> {
if self.v1.is_some() {
let u = self.v1.as_ref().unwrap();
let res = check_orthogonality::<F>(u);
res?;
}
if self.v2.is_some() {
let v = self.v2.as_ref().unwrap();
if log_enabled!(Trace) {
println!("\n\n dumping v");
dump::<F>(&v.view());
}
let res = check_orthogonality::<F>(v);
res?;
}
Ok(())
} }
pub(crate) fn dump<F>(a: &ArrayView2<F>)
where
F: Float + Lapack,
{
for i in 0..a.dim().0 {
println!();
for j in 0..a.dim().1 {
print!("{:.3e} ", a[[i, j]]);
}
}
}
pub(crate) fn check_orthogonality<F>(u: &Array2<F>) -> Result<(), ()>
where
F: Float + Lapack,
{
let epsil = 1.0E-5;
let id: Array2<F> = u.dot(&u.t());
if log_enabled!(Trace) {
println!("\n\n\n dump a*t(a)");
dump::<F>(&id.view());
}
let n = id.dim().0;
for i in 0..n {
if (1. - id[[i, i]].to_f64().unwrap()).abs() > epsil {
log::error!("check_orthogonality failed at ({},{})", i, i);
return Err(());
}
for j in 0..i {
if (id[[i, j]].to_f64().unwrap()).abs() > epsil {
log::error!("check_orthogonality failed at ({},{})", i, j);
return Err(());
}
}
}
Ok(())
}
impl<'a, F> GSvd<'a, F>
where
F: Float + Lapack + ndarray::ScalarOperand + sprs::MulAcc,
{
pub fn new(a: &'a mut Array2<F>, b: &'a mut Array2<F>) -> Self {
assert!(a.is_standard_layout());
assert!(b.is_standard_layout());
if a.dim().1 != b.dim().1 {
log::error!("The two matrices for gsvd must have the same number of columns");
println!("The two matrices for gsvd must have the same number of columns");
panic!("Error constructiing Gsvd problem");
}
GSvd {
a,
b,
opt_params: None,
}
}
pub fn get_parameters(&self) -> &Option<GSvdOptParams> {
&self.opt_params
}
pub fn do_gsvd(&mut self) -> Result<GSvdResult<F>, anyhow::Error> {
log::debug!("entering hope::gsvd do_gsvd");
let cpu_start = ProcessTime::now();
let sys_start = SystemTime::now();
let (a_nbrow, a_nbcol) = self.a.dim();
let jobu = b'U';
let jobv = b'V';
let jobq = b'N'; assert_eq!(a_nbcol, self.b.dim().1); let mut k: i32 = 0;
let mut l: i32 = 0;
let lda: i32 = a_nbcol as i32;
let b_dim = self.b.dim();
let ldb: i32 = b_dim.1 as i32;
let _ires: i32;
let ldu = a_nbrow as i32; let ldv = b_dim.0 as i32; let ldq: i32 = a_nbcol as i32; let mut iwork = Array1::<i32>::zeros(a_nbcol);
let u: Array2<F>;
let v: Array2<F>;
let alpha: Array1<F>;
let beta: Array1<F>;
let mut gsvdres = GSvdResult::<F>::new();
if TypeId::of::<F>() == TypeId::of::<f32>() {
let mut alpha_f32 = Array1::<f32>::zeros(a_nbcol);
let mut beta_f32 = Array1::<f32>::zeros(a_nbcol);
let mut u_f32 = Array2::<f32>::zeros((a_nbrow, a_nbrow));
let mut v_f32 = Array2::<f32>::zeros((b_dim.0, b_dim.0));
let mut q_f32 = Array2::<f32>::zeros((1, a_nbrow));
_ires = unsafe {
let af32 = std::slice::from_raw_parts_mut(
self.a.as_slice_mut().unwrap().as_ptr() as *mut f32,
self.a.len(),
);
let bf32 = std::slice::from_raw_parts_mut(
self.b.as_slice_mut().unwrap().as_ptr() as *mut f32,
self.b.len(),
);
let ires = sggsvd3(
Layout::RowMajor,
jobu,
jobv,
jobq,
a_nbrow.try_into().unwrap(),
a_nbcol.try_into().unwrap(),
self.b.dim().0.try_into().unwrap(),
&mut k,
&mut l,
af32,
lda,
bf32,
ldb,
alpha_f32.as_slice_mut().unwrap(),
beta_f32.as_slice_mut().unwrap(),
u_f32.as_slice_mut().unwrap(),
ldu,
v_f32.as_slice_mut().unwrap(),
ldv,
q_f32.as_slice_mut().unwrap(),
ldq,
iwork.as_slice_mut().unwrap(),
);
if ires == 0 {
u = ndarray::ArrayView::<F, Ix2>::from_shape_ptr(
u_f32.dim(),
u_f32.as_ptr() as *const F,
)
.into_owned();
v = ndarray::ArrayView::<F, Ix2>::from_shape_ptr(
v_f32.dim(),
v_f32.as_ptr() as *const F,
)
.into_owned();
alpha = ndarray::ArrayView::<F, Ix1>::from_shape_ptr(
alpha_f32.len(),
alpha_f32.as_ptr() as *const F,
)
.into_owned();
beta = ndarray::ArrayView::<F, Ix1>::from_shape_ptr(
beta_f32.len(),
beta_f32.as_ptr() as *const F,
)
.into_owned();
gsvdres.init_from_lapack(
a_nbrow.try_into().unwrap(),
a_nbcol.try_into().unwrap(),
b_dim.0.try_into().unwrap(),
u,
v,
i64::from(k),
i64::from(l),
alpha,
beta,
iwork,
);
} else if ires == 1 {
log::error!("lapacke::sggsvd3 returned err code 1");
return Err(anyhow!("lapack for f64 failed to converge"));
} else if ires < 0 {
log::error!("lapacke::sggsvd3 returned err code {}", ires);
return Err(anyhow!("argument {} had an illegal value", -ires));
}
ires
}; }
else if TypeId::of::<F>() == TypeId::of::<f64>() {
let mut alpha_f64 = Array1::<f64>::zeros(a_nbcol);
let mut beta_f64 = Array1::<f64>::zeros(a_nbcol);
let mut u_f64 = Array2::<f64>::zeros((a_nbrow, a_nbrow));
let mut v_f64 = Array2::<f64>::zeros((b_dim.0, b_dim.0));
let mut q_f64 = Array2::<f64>::zeros((1, a_nbcol));
_ires = unsafe {
let af64 = std::slice::from_raw_parts_mut(
self.a.as_slice_mut().unwrap().as_mut_ptr() as *mut f64,
self.a.len(),
);
let bf64 = std::slice::from_raw_parts_mut(
self.b.as_slice_mut().unwrap().as_mut_ptr() as *mut f64,
self.b.len(),
);
let ires = dggsvd3(
Layout::RowMajor,
jobu,
jobv,
jobq,
a_nbrow.try_into().unwrap(),
a_nbcol.try_into().unwrap(),
self.b.dim().0.try_into().unwrap(),
&mut k,
&mut l,
af64,
lda,
bf64,
ldb,
alpha_f64.as_slice_mut().unwrap(),
beta_f64.as_slice_mut().unwrap(),
u_f64.as_slice_mut().unwrap(),
ldu,
v_f64.as_slice_mut().unwrap(),
ldv,
q_f64.as_slice_mut().unwrap(),
ldq,
iwork.as_slice_mut().unwrap(),
);
if ires == 0 {
u = ndarray::ArrayView::<F, Ix2>::from_shape_ptr(
u_f64.dim(),
u_f64.as_ptr() as *const F,
)
.into_owned();
v = ndarray::ArrayView::<F, Ix2>::from_shape_ptr(
v_f64.dim(),
v_f64.as_ptr() as *const F,
)
.into_owned();
alpha = ndarray::ArrayView::<F, Ix1>::from_shape_ptr(
alpha_f64.len(),
alpha_f64.as_ptr() as *const F,
)
.into_owned();
beta = ndarray::ArrayView::<F, Ix1>::from_shape_ptr(
beta_f64.len(),
beta_f64.as_ptr() as *const F,
)
.into_owned();
gsvdres.init_from_lapack(
a_nbrow.try_into().unwrap(),
a_nbcol.try_into().unwrap(),
b_dim.0.try_into().unwrap(),
u,
v,
i64::from(k),
i64::from(l),
alpha,
beta,
iwork,
);
} else if ires == 1 {
log::error!("lapack for f64 failed to converge returned err code 1");
return Err(anyhow!("lapack for f64 failed to converge"));
} else if ires < 0 {
return Err(anyhow!("argument {} had an illegal value", -ires));
}
ires
} }
else {
log::error!("do_approx_gsvd only implemented for f32 and f64");
panic!();
}
log::info!(
"do_gsvd sys time(s) {:.2e} cpu time(s) {:.2e}",
sys_start.elapsed().unwrap().as_secs(),
cpu_start.elapsed().as_secs()
);
if log_enabled!(log::Level::Debug) {
gsvdres.debug_print();
}
Ok(gsvdres)
} }
#[cfg(test)]
mod tests {
use super::*;
use ndarray::{ArrayBase, array};
fn log_init_test() {
let _ = env_logger::builder().is_test(true).try_init();
}
fn small_lapack_gsvd(a: &mut Array2<f64>, b: &mut Array2<f64>) -> GSvdResult<f64> {
let (a_nbrow, a_nbcol) = a.dim();
log::debug!("a dims : ({}, {})", a_nbrow, a_nbcol);
let jobu = 'U' as u8; let jobv = b'V'; let jobq = b'Q' as u8; assert_eq!(a_nbcol, b.dim().1); let mut k: i32 = 0;
let mut l: i32 = 0;
let lda: i32 = a_nbcol as i32; let b_dim = b.dim();
log::debug!("b dims : ({}, {})", b_dim.0, b_dim.1);
let ldb: i32 = b_dim.1 as i32; let mut alpha_f64 = Array1::<f64>::zeros(a_nbcol);
let mut beta_f64 = Array1::<f64>::zeros(a_nbcol);
let mut u_f64 = Array2::<f64>::zeros((a_nbrow, a_nbrow));
let mut v_f64 = Array2::<f64>::zeros((b_dim.0, b_dim.0));
let mut q_f64 = Array2::<f64>::zeros((a_nbcol, a_nbcol));
let ldu = a_nbrow as i32; let ldv = b_dim.0 as i32;
let ldq = a_nbcol as i32; let mut iwork = Array1::<i32>::zeros(a_nbcol);
let ires = unsafe {
let a_slice = std::slice::from_raw_parts_mut(
a.as_slice_mut().unwrap().as_ptr() as *mut f64,
a.len(),
);
let b_slice = std::slice::from_raw_parts_mut(
b.as_slice_mut().unwrap().as_ptr() as *mut f64,
b.len(),
);
dggsvd3(
Layout::RowMajor,
jobu,
jobv,
jobq,
a_nbrow.try_into().unwrap(),
a_nbcol.try_into().unwrap(),
b.dim().0.try_into().unwrap(),
&mut k,
&mut l,
a_slice,
lda,
b_slice,
ldb,
alpha_f64.as_slice_mut().unwrap(),
beta_f64.as_slice_mut().unwrap(),
u_f64.as_slice_mut().unwrap(),
ldu,
v_f64.as_slice_mut().unwrap(),
ldv,
q_f64.as_slice_mut().unwrap(),
ldq,
iwork.as_slice_mut().unwrap(),
)
};
if ires != 0 {
println!("ggsvd3 returned {}", ires);
log::error!("dggsvd3 returned {}", ires);
assert!(1 == 0);
}
log::debug!("dggsvd3 passed");
let mut gsvdres = GSvdResult::<f64>::new();
gsvdres.init_from_lapack(
a_nbrow.try_into().unwrap(),
a_nbcol.try_into().unwrap(),
b_dim.0.try_into().unwrap(),
u_f64,
v_f64,
k.into(),
l.into(),
alpha_f64,
beta_f64,
iwork,
);
gsvdres
}
#[test]
fn test_lapack_gsvd_array_1() {
log_init_test();
let mut a = array![
[1., 6., 11.],
[2., 7., 12.],
[3., 8., 13.],
[4., 9., 14.],
[5., 10., 15.]
];
let mut b = array![[8., 1., 6.], [3., 5., 7.], [4., 9., 2.]];
let gsvdres = small_lapack_gsvd(&mut a, &mut b);
gsvdres.dump_u();
gsvdres.dump_v();
gsvdres.debug_print();
let s1 = gsvdres.get_s1().unwrap();
let s2 = gsvdres.get_s2().unwrap();
for i in 0..s1.len() {
log::debug!("s1[i] : {:.5e}, s2[i] : {:.5e}", s1[i], s2[i]);
}
assert!((s1[0] - 0.98067).abs() < 1.0e-5);
assert!((s2[0] - 1.95655e-1).abs() < 1.0e-5);
assert!((s1[1] - 3.15531e-1).abs() < 1.0e-5);
assert!((s2[1] - 9.48915e-1).abs() < 1.0e-5);
let res = gsvdres.check_uv_orthogonal();
assert!(res.is_ok());
}
#[test]
fn test_lapack_gsvd_array_2() {
log_init_test();
let mut a = array![[1., 2., 3., 3., 2., 1.], [4., 5., 6., 7., 8., 8.]];
let mut b = array![
[1., 2., 3., 4., 5., 6.],
[7., 8., 9., 10., 11., 12.],
[13., 14., 15., 16., 17., 18.]
];
let gsvdres = small_lapack_gsvd(&mut a, &mut b);
gsvdres.dump_u();
gsvdres.dump_v();
let res = gsvdres.check_uv_orthogonal();
let s1 = gsvdres.get_s1().unwrap();
let s2 = gsvdres.get_s2().unwrap();
log::debug!("s.len() : {}", s1.len());
for i in 0..s1.len() {
log::debug!("s1[i] : {:.5e}, s2[i] : {:.5e}", s1[i], s2[i]);
}
assert_eq!(gsvdres.get_k(), 2);
assert_eq!(gsvdres.get_l(), 2);
assert!(res.is_ok());
}
use rand::Rng;
use rand_distr::StandardNormal;
use rand_xoshiro::Xoshiro256PlusPlus;
use rand_xoshiro::rand_core::SeedableRng;
#[test]
fn test_lapack_gsvd_random() {
log_init_test();
let mut rng = Xoshiro256PlusPlus::seed_from_u64(4664397);
let stdnormal = StandardNormal {};
let dima = [3, 70];
let mut a: Array2<f64> = ArrayBase::from_shape_fn(dima, |_| rng.sample(stdnormal));
let dimb = [22, 70];
let mut b: Array2<f64> = ArrayBase::from_shape_fn(dimb, |_| rng.sample(stdnormal));
let gsvdres = small_lapack_gsvd(&mut a, &mut b);
gsvdres.dump_u();
gsvdres.dump_v();
let _res = gsvdres.check_uv_orthogonal();
let s1 = gsvdres.get_s1().unwrap();
let s2 = gsvdres.get_s2().unwrap();
for i in 0..s1.len() {
log::debug!("s1[i] : {:.5e}, s2[i] : {:.5e}", s1[i], s2[i]);
}
}
}