use rayon::prelude::*;
use rustc_hash::{FxHashMap, FxHashSet};
use std::time::Instant;
#[derive(Clone, Copy, Default)]
pub enum SnnSimilarityMethod {
#[default]
Intersection,
Rank,
}
pub fn get_snn_similiarity_method(s: &str) -> Option<SnnSimilarityMethod> {
match s.to_lowercase().as_str() {
"jaccard" => Some(SnnSimilarityMethod::Intersection),
"rank" => Some(SnnSimilarityMethod::Rank),
_ => None,
}
}
pub fn generate_snn_full(
flat_knn: &[usize],
k: usize,
n_samples: usize,
pruning: f32,
method: SnnSimilarityMethod,
verbose: bool,
) -> (Vec<usize>, Vec<f32>) {
let mut reverse_mappings: Vec<Vec<(usize, usize)>> = vec![Vec::new(); n_samples];
let start_time = Instant::now();
for i in 0..n_samples {
reverse_mappings[i].push((i, 0));
for neighbor_idx in 0..k {
let neighbor = flat_knn[neighbor_idx * n_samples + i];
reverse_mappings[neighbor].push((i, neighbor_idx + 1));
}
}
let results: Vec<(usize, usize, f32)> = (0..n_samples)
.into_par_iter()
.flat_map(|j| {
let mut scores = vec![0.0f32; n_samples];
let mut added = Vec::new();
for i in 0..=k {
let cur_neighbor = if i == 0 {
j
} else {
flat_knn[(i - 1) * n_samples + j]
};
for &(othernode, other_rank) in &reverse_mappings[cur_neighbor] {
if othernode < j {
match method {
SnnSimilarityMethod::Rank => {
let combined_rank = (i + other_rank) as f32;
if scores[othernode] == 0.0 {
scores[othernode] = combined_rank;
added.push(othernode);
} else if combined_rank < scores[othernode] {
scores[othernode] = combined_rank;
}
}
SnnSimilarityMethod::Intersection => {
if scores[othernode] == 0.0 {
added.push(othernode);
}
scores[othernode] += 1.0;
}
}
}
}
}
added
.into_iter()
.filter_map(|othernode| {
let weight = match method {
SnnSimilarityMethod::Rank => {
let preliminary = k as f32 - scores[othernode] / 2.0;
let raw_weight = preliminary.max(1e-6);
raw_weight / k as f32
}
SnnSimilarityMethod::Intersection => {
scores[othernode] / (2.0 * (k as f32 + 1.0) - scores[othernode])
}
};
if weight >= pruning {
Some((j, othernode, weight))
} else {
None
}
})
.collect::<Vec<_>>()
})
.collect();
let mut edges = Vec::with_capacity(results.len() * 2);
let mut weights = Vec::with_capacity(results.len());
for (i, j, weight) in results {
edges.push(i);
edges.push(j);
weights.push(weight);
}
let end_snn = start_time.elapsed();
if verbose {
println!("Transformed kNN into a full sNN graph: {:.2?}", end_snn);
}
(edges, weights)
}
pub fn generate_snn_limited(
flat_knn: &[usize],
k: usize,
n_samples: usize,
pruning: f32,
method: SnnSimilarityMethod,
verbose: bool,
) -> (Vec<usize>, Vec<f32>) {
let start_time = Instant::now();
let edge_map: FxHashMap<(usize, usize), f32> = (0..n_samples)
.into_par_iter()
.flat_map(|i| {
let mut edges = Vec::new();
for neighbor_idx in 0..k {
let j = flat_knn[neighbor_idx * n_samples + i];
let weight = match method {
SnnSimilarityMethod::Intersection => {
let neighbors_i: FxHashSet<usize> = (0..k)
.map(|idx| flat_knn[idx * n_samples + i])
.chain(std::iter::once(i)) .collect();
let neighbors_j: FxHashSet<usize> = (0..k)
.map(|idx| flat_knn[idx * n_samples + j])
.chain(std::iter::once(j)) .collect();
let intersection_count =
neighbors_i.intersection(&neighbors_j).count() as f32;
intersection_count / (2.0 * (k as f32 + 1.0) - intersection_count)
}
SnnSimilarityMethod::Rank => {
let mut ranks_i = FxHashMap::default();
ranks_i.insert(i, 0); for (rank, neighbor) in
(0..k).map(|idx| flat_knn[idx * n_samples + i]).enumerate()
{
ranks_i.insert(neighbor, rank + 1);
}
let mut ranks_j = FxHashMap::default();
ranks_j.insert(j, 0); for (rank, neighbor) in
(0..k).map(|idx| flat_knn[idx * n_samples + j]).enumerate()
{
ranks_j.insert(neighbor, rank + 1);
}
let min_combined_rank = ranks_i
.keys()
.filter(|&neighbor| ranks_j.contains_key(neighbor))
.map(|neighbor| ranks_i[neighbor] + ranks_j[neighbor])
.min()
.unwrap_or(2 * k)
as f32;
let preliminary = k as f32 - min_combined_rank / 2.0;
let raw_weight = preliminary.max(1e-6);
raw_weight / k as f32
}
};
if weight >= pruning {
let edge_key = if i < j { (i, j) } else { (j, i) };
edges.push((edge_key, weight));
}
}
edges
})
.collect::<Vec<_>>()
.into_iter()
.fold(FxHashMap::default(), |mut acc, (edge_key, weight)| {
acc.entry(edge_key)
.and_modify(|existing_weight| {
if weight > *existing_weight {
*existing_weight = weight;
}
})
.or_insert(weight);
acc
});
let mut edges = Vec::with_capacity(edge_map.len() * 2);
let mut weights = Vec::with_capacity(edge_map.len());
for ((i, j), weight) in edge_map {
edges.push(i);
edges.push(j);
weights.push(weight);
}
let end_snn = start_time.elapsed();
if verbose {
println!("Transformed kNN into an sNN graph: {:.2?}", end_snn);
}
(edges, weights)
}