#![allow(clippy::needless_range_loop)]
#[allow(unused)]
use anyhow::anyhow;
use log::log_enabled;
use ndarray::{Array1, Array2};
use sprs::{CsMatI, TriMatI};
use ahash::AHasher;
use probminhash::probminhasher::*;
use std::collections::HashMap;
use parking_lot::RwLock;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use std::sync::Arc;
use cpu_time::ProcessTime;
use std::time::SystemTime;
use super::{params::NodeSketchParams, sla::*};
use crate::embedding::{Embedded, EmbedderT};
pub(crate) fn jaccard_distance_usize(v1: &[usize], v2: &[usize]) -> f64 {
assert_eq!(v1.len(), v2.len());
let common = v1
.iter()
.zip(v2.iter())
.fold(0usize, |acc, v| if v.0 == v.1 { acc + 1 } else { acc });
1. - (common as f64) / (v1.len() as f64)
}
pub type RowSketch = Arc<RwLock<Array1<usize>>>;
pub struct NodeSketch {
params: NodeSketchParams,
csrmat: CsMatI<f64, usize>,
sketches: Vec<RowSketch>,
previous_sketches: Vec<RowSketch>,
}
impl NodeSketch {
pub fn new(params: NodeSketchParams, mut trimat: TriMatI<f64, usize>) -> Self {
let csrmat = diagonal_augmentation(&mut trimat, 1.);
log::debug!(
" NodeSketch new csrmat dims nb_rows {}, nb_cols {} ",
csrmat.rows(),
csrmat.cols()
);
log::info!("=======================================================");
log::info!(
"sketching symetric params dimension : {}, nbhops : {}, decay : {:.3e}",
params.get_sketch_size(),
params.get_nb_iter(),
params.get_decay_weight()
);
log::info!("=======================================================");
let mut sketches = Vec::<RowSketch>::with_capacity(csrmat.rows());
let mut previous_sketches = Vec::<RowSketch>::with_capacity(csrmat.rows());
for _ in 0..csrmat.rows() {
let sketch = Array1::<usize>::zeros(params.get_sketch_size());
sketches.push(Arc::new(RwLock::new(sketch)));
let previous_sketch = Array1::<usize>::zeros(params.get_sketch_size());
previous_sketches.push(Arc::new(RwLock::new(previous_sketch)));
}
NodeSketch {
params,
csrmat,
sketches,
previous_sketches,
}
}
pub fn get_sketch_size(&self) -> usize {
self.params.sketch_size
}
pub fn get_decay_weight(&self) -> f64 {
self.params.decay
}
pub fn get_nb_nodes(&self) -> usize {
self.csrmat.rows()
}
#[allow(unused)]
pub(crate) fn dump_row_iteration(&self, noderank: usize) {
println!("row iteration i : {}", noderank);
println!(
"previous state {:?}",
self.previous_sketches[noderank].read()
);
println!("new state {:?} ", self.sketches[noderank].read());
}
fn sketch_slamatrix(&mut self, parallel: bool) {
let treat_row = |row: usize| {
let mut probminhash3 = ProbMinHash3::<usize, AHasher>::new(self.get_sketch_size(), row);
let col_range = self.csrmat.indptr().outer_inds_sz(row);
log::trace!("sketch_slamatrix i : {}, col_range : {:?}", row, col_range);
for k in col_range {
let j = self.csrmat.indices()[k];
let w = self.csrmat.data()[k];
probminhash3.hash_item(j, &w);
}
let sketch = probminhash3.get_signature();
log::trace!(
" sketch_slamatrix sketch row i : {} , sketch : {:?}",
row,
sketch
);
for j in 0..self.get_sketch_size() {
self.previous_sketches[row].write()[j] = sketch[j];
}
};
if !parallel {
log::debug!(" not parallel case nb rows {}", self.csrmat.rows());
for row in 0..self.csrmat.rows() {
if self.csrmat.indptr().nnz_in_outer_sz(row) > 0 {
log::trace!("sketching row {}", row);
treat_row(row);
}
}
} else {
(0..self.csrmat.rows()).into_par_iter().for_each(|row| {
if self.csrmat.indptr().nnz_in_outer_sz(row) > 0 {
treat_row(row);
}
})
}
log::debug!("sketch_slamatrix done")
}
pub fn compute_embedded(&mut self) -> Result<Embedded<usize>, anyhow::Error> {
log::debug!("in Nodesketch::compute_Embedded");
let cpu_start = ProcessTime::now();
let sys_start = SystemTime::now();
let parallel = self.params.parallel;
self.sketch_slamatrix(parallel);
for _ in 0..self.params.nb_iter {
if parallel {
self.parallel_iteration();
} else {
self.iteration();
}
}
let sys_t: f64 = sys_start.elapsed().unwrap().as_millis() as f64 / 1000.;
println!(
" embedding sys time(s) {:.2e} cpu time(s) {:.2e}",
sys_t,
cpu_start.elapsed().as_secs()
);
let nbnodes = self.sketches.len();
let dim = self.sketches[0].read().len();
let mut embedded = Array2::<usize>::zeros((nbnodes, dim));
for i in 0..nbnodes {
for j in 0..self.get_sketch_size() {
embedded.row_mut(i)[j] = self.sketches[i].read()[j];
}
}
let embedded = Embedded::<usize>::new(embedded, jaccard_distance_usize);
Ok(embedded)
}
fn iteration(&mut self) {
for (row, _) in self.csrmat.outer_iterator().enumerate() {
self.treat_row(&row);
if log_enabled!(log::Level::Trace) {
log::trace!("dump end of iteration : ");
self.dump_row_iteration(row);
}
} for i in 0..self.get_nb_nodes() {
let mut row_write = self.previous_sketches[i].write();
for j in 0..self.get_sketch_size() {
row_write[j] = self.sketches[i].read()[j];
}
}
}
fn parallel_iteration(&mut self) {
(0..self.csrmat.rows())
.into_par_iter()
.for_each(|row| self.treat_row(&row));
for i in 0..self.get_nb_nodes() {
let mut row_write = self.previous_sketches[i].write();
for j in 0..self.get_sketch_size() {
row_write[j] = self.sketches[i].read()[j];
}
}
}
fn treat_row(&self, row: &usize) {
let row_vec = self.csrmat.outer_view(*row);
if row_vec.is_none() {
return;
}
let row_vec = row_vec.unwrap();
let mut v_k = HashMap::<usize, f64, ahash::RandomState>::default();
let weight = self.get_decay_weight() / self.get_sketch_size() as f64;
let mut row_iter = row_vec.iter();
while let Some(neighbour) = row_iter.next() {
match v_k.get_mut(&neighbour.0) {
Some(val) => {
*val += *neighbour.1;
log::trace!(
"{} augmenting weight in v_k for neighbour {}, new weight {:.3e}",
neighbour.0,
*neighbour.1,
*val
);
}
None => {
log::trace!(
"adding node in v_k {} weight {:.3e}",
neighbour.0,
*neighbour.1
);
v_k.insert(neighbour.0, *neighbour.1);
}
};
let neighbour_sketch = &*self.previous_sketches[neighbour.0].read();
for n in neighbour_sketch {
match v_k.get_mut(n) {
Some(val) => {
*val += weight * *neighbour.1;
log::trace!(
"{} sketch augmenting node {} weight in v_k with decayed edge weight {:.3e} new weight {:.3e}",
neighbour.0,
*n,
weight * *neighbour.1,
*val
);
}
None => {
log::trace!(
"{} sketch adding node with {} decayed weight {:.3e}",
neighbour.0,
*n,
weight * *neighbour.1
);
v_k.insert(*n, weight * neighbour.1);
}
};
}
} let mut probminhash3a = ProbMinHash3a::<usize, AHasher>::new(self.get_sketch_size(), *row);
probminhash3a.hash_weigthed_hashmap(&v_k);
let sketch = Array1::from_vec(probminhash3a.get_signature().clone());
let mut row_write = self.sketches[*row].write();
for j in 0..self.get_sketch_size() {
row_write[j] = sketch[j];
}
} }
impl EmbedderT<usize> for NodeSketch {
type Output = Embedded<usize>;
fn embed(&mut self) -> Result<Embedded<usize>, anyhow::Error> {
self.compute_embedded()
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
fn log_init_test() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test]
fn test_nodesketch_lesmiserables() {
log_init_test();
log::debug!("in nodesketch.rs test_nodesketch_lesmiserables");
let path = std::path::Path::new(crate::DATADIR)
.join("moreno_lesmis")
.join("out.moreno_lesmis_lesmis");
log::info!(
"\n\n test_nodesketch_lesmiserables, loading file {:?}",
path
);
let res = csv_to_trimat::<f64>(&path, false, b' ');
if res.is_err() {
log::error!("test_nodesketch_lesmiserables failed in csv_to_trimat");
assert_eq!(1, 0);
}
let (trimat, node_index) = res.unwrap();
let sketch_size = 20;
let decay = 0.25;
let nb_iter = 4;
let parallel = false;
let symetric = true;
let params = NodeSketchParams {
sketch_size,
decay,
nb_iter,
symetric,
parallel,
};
let mut nodesketch = NodeSketch::new(params, trimat);
let sketch_embedding = Embedding::new(node_index, &mut nodesketch);
if sketch_embedding.is_err() {
log::error!("test_nodesketch_lesmiserables failed in compute_Embedded");
assert_eq!(1, 0);
}
let embed_res = sketch_embedding.unwrap();
let dist_11_27 = embed_res.get_node_distance(11, 27);
log::debug!(
"node (11,27) = rank({},{})",
embed_res.get_node_rank(11).unwrap(),
embed_res.get_node_rank(27).unwrap()
);
log::debug!(
"distance between nodes 11 and 27 : {}, weight in file {} ",
dist_11_27,
31
);
let dist_11_33 = embed_res.get_node_distance(11, 33);
log::debug!(
"distance between nodes 11 and 33 {} , weight in file : {}",
dist_11_33,
1
);
let embedded = embed_res.get_embedded_data();
let rank = embed_res.get_node_rank(11).unwrap();
log::debug!(
"\n\n row {:?} sketch {:?} ",
rank,
embedded.get_embedded().row(rank)
);
let rank = embed_res.get_node_rank(27).unwrap();
log::trace!(
"\n\n row {:?} sketch {:?} ",
rank,
embedded.get_embedded().row(rank)
);
let dist_21_23 = embed_res.get_node_distance(21, 23);
log::debug!(
"embedded distance between nodes 21 and 23 : {:3.e}",
dist_21_23
);
let rank = 48;
log::trace!(
"\n\n row {:?} , node_id {:?}, sketch {:?} ",
rank,
embed_res.get_node_id(rank),
embedded.get_embedded().row(rank)
);
let rank = 50;
log::trace!(
"\n\n row {:?}, node_id {:?}, sketch {:?} ",
rank,
embed_res.get_node_id(rank),
embedded.get_embedded().row(rank)
);
let node_of_rank_26 = *embed_res.get_node_id(26).unwrap();
let node_of_rank_27 = *embed_res.get_node_id(27).unwrap();
let dist = embed_res.get_node_distance(node_of_rank_26, node_of_rank_27);
log::debug!(
"distance between nodes n1 : {} n2 : {}, dist : {:3.e}",
node_of_rank_26,
node_of_rank_27,
dist
);
} }