use std::collections::HashMap;
use ndarray::{Array1, Array2, Axis};
use sprs::{CsMat, TriMatBase};
use ndarray_linalg::SVDDC;
use crate::tools::{nodeparam::*, svdapprox::*};
const FULL_MAT_REPR: usize = 5000;
const FULL_SVD_SIZE_LIMIT: usize = 5000;
pub(crate) struct GraphLaplacian {
sym_laplacian: MatRepr<f32>,
pub(crate) degrees: Array1<f32>,
_s: Option<Array1<f32>>,
_u: Option<Array2<f32>>,
}
impl GraphLaplacian {
pub fn new(sym_laplacian: MatRepr<f32>, degrees: Array1<f32>) -> Self {
GraphLaplacian {
sym_laplacian,
degrees,
_s: None,
_u: None,
}
}
#[inline]
fn is_csr(&self) -> bool {
self.sym_laplacian.is_csr()
}
fn get_nbrow(&self) -> usize {
self.degrees.len()
}
fn do_full_svd(&mut self) -> Result<SvdResult<f32>, String> {
log::info!("GraphLaplacian doing full svd");
let b = self.sym_laplacian.get_full_mut().unwrap();
log::trace!(
"GraphLaplacian ... size nbrow {} nbcol {} ",
b.shape()[0],
b.shape()[1]
);
let slice_for_svd_opt = b.as_slice_mut();
if slice_for_svd_opt.is_none() {
println!("direct_svd Matrix cannot be transformed into a slice : not contiguous or not in standard order");
return Err(String::from("not contiguous or not in standard order"));
}
log::trace!("direct_svd calling svddc driver");
let res_svd_b = b.svddc(JobSvd::Some);
if res_svd_b.is_err() {
log::info!("GraphLaplacian do_full_svd svddc failed");
return Err(String::from("GraphLaplacian svddc failed"));
};
let res_svd_b = res_svd_b.unwrap();
let s: Array1<f32> = res_svd_b.1;
Ok(SvdResult {
s: Some(s),
u: res_svd_b.0,
vt: None,
})
}
fn do_approx_svd(&mut self, asked_dim: usize) -> Result<SvdResult<f32>, String> {
assert!(asked_dim >= 2);
log::info!(
"got laplacian, going to approximated svd ... asked_dim : {}",
asked_dim
);
let mut svdapprox = SvdApprox::new(&self.sym_laplacian);
let svdmode = RangeApproxMode::RANK(RangeRank::new(20, 5));
let svd_res = svdapprox.direct_svd(svdmode);
log::trace!("exited svd");
if !svd_res.is_ok() {
println!("svd approximation failed");
std::panic!();
}
return svd_res;
}
pub fn do_svd(&mut self, asked_dim: usize) -> Result<SvdResult<f32>, String> {
if !self.is_csr() && self.get_nbrow() <= FULL_SVD_SIZE_LIMIT {
self.do_full_svd()
} else {
self.do_approx_svd(asked_dim)
}
} }
pub(crate) fn get_laplacian(initial_space: &NodeParams) -> GraphLaplacian {
log::debug!("in get_laplacian");
let nbnodes = initial_space.get_nb_nodes();
let max_nbng = initial_space.get_max_nbng();
let node_params = initial_space;
if nbnodes <= FULL_MAT_REPR {
log::debug!("get_laplacian using full matrix");
let mut transition_proba = Array2::<f32>::zeros((nbnodes, nbnodes));
for i in 0..node_params.params.len() {
let node_param = node_params.get_node_param(i);
for j in 0..node_param.edges.len() {
let edge = node_param.edges[j];
transition_proba[[i, edge.node]] = edge.weight;
} } log::trace!("full matrix initialized");
let mut symgraph = (&transition_proba + &transition_proba.view().t()) * 0.5;
let diag = symgraph.sum_axis(Axis(1));
for i in 0..nbnodes {
let mut row = symgraph.row_mut(i);
for j in 0..nbnodes {
row[[j]] /= (diag[[i]] * diag[[j]]).sqrt();
}
}
log::trace!("\n allocating full matrix laplacian");
let laplacian = GraphLaplacian::new(MatRepr::from_array2(symgraph), diag);
laplacian
} else {
log::debug!("Embedder using csr matrix");
let mut edge_list = HashMap::<(usize, usize), f32>::with_capacity(nbnodes * max_nbng);
for i in 0..node_params.params.len() {
let node_param = node_params.get_node_param(i);
for j in 0..node_param.edges.len() {
let edge = node_param.edges[j];
edge_list.insert((i, edge.node), node_param.edges[j].weight);
} }
let mut diagonal = Array1::<f32>::zeros(nbnodes);
let mut rows = Vec::<usize>::with_capacity(nbnodes * 2 * max_nbng);
let mut cols = Vec::<usize>::with_capacity(nbnodes * 2 * max_nbng);
let mut values = Vec::<f32>::with_capacity(nbnodes * 2 * max_nbng);
for ((i, j), val) in edge_list.iter() {
assert!(i != j);
let sym_val;
if let Some(t_val) = edge_list.get(&(*j, *i)) {
sym_val = (val + t_val) * 0.5;
} else {
sym_val = *val;
}
rows.push(*i);
cols.push(*j);
values.push(sym_val);
diagonal[*i] += sym_val;
rows.push(*j);
cols.push(*i);
values.push(sym_val);
diagonal[*j] += sym_val;
}
for i in 0..rows.len() {
let row = rows[i];
let col = cols[i];
if row != col {
values[i] = values[i] / (diagonal[row] * diagonal[col]).sqrt();
}
}
log::trace!("allocating csr laplacian");
let laplacian = TriMatBase::<Vec<usize>, Vec<f32>>::from_triplets(
(nbnodes, nbnodes),
rows,
cols,
values,
);
let csr_mat: CsMat<f32> = laplacian.to_csr();
let laplacian = GraphLaplacian::new(MatRepr::from_csrmat(csr_mat), diagonal);
laplacian
} }