use std::collections::HashSet;
use crate::catalog::Pattern;
use crate::census::{count, Selector};
use crate::snapshot::{GraphAdapter, Snapshot};
fn compact_map<G: GraphAdapter>(g: G) -> Vec<usize> {
let bound = g.node_bound();
let mut compact = vec![usize::MAX; bound];
for (idx, v) in g.node_identifiers().enumerate() {
compact[g.to_index(v)] = idx;
}
compact
}
#[derive(Clone, Debug, PartialEq)]
pub struct LinkPredictionScores {
pub common_neighbors: usize,
pub jaccard: f64,
pub adamic_adar: f64,
pub resource_allocation: f64,
pub preferential_attachment: usize,
}
pub fn common_neighbors<G: GraphAdapter>(g: G, u: G::NodeId, v: G::NodeId) -> usize {
let compact = compact_map(g);
let snap = Snapshot::new(g);
let ui = compact[g.to_index(u)];
let vi = compact[g.to_index(v)];
let nu: HashSet<usize> = snap.neighbors(ui).iter().copied().collect();
snap.neighbors(vi)
.iter()
.filter(|&&w| nu.contains(&w))
.count()
}
pub fn jaccard<G: GraphAdapter>(g: G, u: G::NodeId, v: G::NodeId) -> f64 {
let compact = compact_map(g);
let snap = Snapshot::new(g);
let ui = compact[g.to_index(u)];
let vi = compact[g.to_index(v)];
let nu: HashSet<usize> = snap.neighbors(ui).iter().copied().collect();
let nv: HashSet<usize> = snap.neighbors(vi).iter().copied().collect();
let inter = nu.intersection(&nv).count();
let union = nu.union(&nv).count();
if union == 0 {
0.0
} else {
inter as f64 / union as f64
}
}
pub fn adamic_adar<G: GraphAdapter>(g: G, u: G::NodeId, v: G::NodeId) -> f64 {
let compact = compact_map(g);
let snap = Snapshot::new(g);
let ui = compact[g.to_index(u)];
let vi = compact[g.to_index(v)];
let nu: HashSet<usize> = snap.neighbors(ui).iter().copied().collect();
snap.neighbors(vi)
.iter()
.filter(|&&w| nu.contains(&w))
.map(|&w| {
let deg = snap.neighbors(w).len();
if deg >= 2 {
1.0 / (deg as f64).ln()
} else {
0.0
}
})
.sum()
}
pub fn resource_allocation<G: GraphAdapter>(g: G, u: G::NodeId, v: G::NodeId) -> f64 {
let compact = compact_map(g);
let snap = Snapshot::new(g);
let ui = compact[g.to_index(u)];
let vi = compact[g.to_index(v)];
let nu: HashSet<usize> = snap.neighbors(ui).iter().copied().collect();
snap.neighbors(vi)
.iter()
.filter(|&&w| nu.contains(&w))
.map(|&w| {
let deg = snap.neighbors(w).len();
if deg >= 1 {
1.0 / deg as f64
} else {
0.0
}
})
.sum()
}
pub fn preferential_attachment<G: GraphAdapter>(g: G, u: G::NodeId, v: G::NodeId) -> usize {
let compact = compact_map(g);
let snap = Snapshot::new(g);
let ui = compact[g.to_index(u)];
let vi = compact[g.to_index(v)];
snap.neighbors(ui).len() * snap.neighbors(vi).len()
}
pub fn score_non_edges<G: GraphAdapter>(g: G) -> Vec<(G::NodeId, G::NodeId, LinkPredictionScores)> {
let snap = Snapshot::new(g);
let n = snap.len();
let mut out = Vec::new();
for i in 0..n {
for j in (i + 1)..n {
if snap.adjacent(i, j) {
continue;
}
let ni: HashSet<usize> = snap.neighbors(i).iter().copied().collect();
let nj: HashSet<usize> = snap.neighbors(j).iter().copied().collect();
let common_vec: Vec<usize> = ni.intersection(&nj).copied().collect();
let common = common_vec.len();
let union = ni.union(&nj).count();
let jacc = if union == 0 {
0.0
} else {
common as f64 / union as f64
};
let aa: f64 = common_vec
.iter()
.map(|&w| {
let deg = snap.neighbors(w).len();
if deg >= 2 {
1.0 / (deg as f64).ln()
} else {
0.0
}
})
.sum();
let ra: f64 = common_vec
.iter()
.map(|&w| {
let deg = snap.neighbors(w).len();
if deg >= 1 {
1.0 / deg as f64
} else {
0.0
}
})
.sum();
let pa = snap.neighbors(i).len() * snap.neighbors(j).len();
out.push((
snap.id(i),
snap.id(j),
LinkPredictionScores {
common_neighbors: common,
jaccard: jacc,
adamic_adar: aa,
resource_allocation: ra,
preferential_attachment: pa,
},
));
}
}
out
}
pub fn node_triangles<G: GraphAdapter>(g: G, u: G::NodeId) -> usize {
let compact = compact_map(g);
let snap = Snapshot::new(g);
let ui = compact[g.to_index(u)];
let nbrs = snap.neighbors(ui);
let mut t = 0;
for a in 0..nbrs.len() {
for b in (a + 1)..nbrs.len() {
if snap.adjacent(nbrs[a], nbrs[b]) {
t += 1;
}
}
}
t
}
pub fn total_triangles<G: GraphAdapter>(g: G) -> usize {
let snap = Snapshot::new(g);
let n = snap.len();
let mut sum = 0usize;
for u in 0..n {
let nbrs = snap.neighbors(u);
for a in 0..nbrs.len() {
for b in (a + 1)..nbrs.len() {
if snap.adjacent(nbrs[a], nbrs[b]) {
sum += 1;
}
}
}
}
sum / 3
}
pub fn local_clustering<G: GraphAdapter>(g: G, u: G::NodeId) -> f64 {
let compact = compact_map(g);
let snap = Snapshot::new(g);
let ui = compact[g.to_index(u)];
let k = snap.neighbors(ui).len();
if k < 2 {
return 0.0;
}
let nbrs = snap.neighbors(ui);
let mut t = 0usize;
for a in 0..nbrs.len() {
for b in (a + 1)..nbrs.len() {
if snap.adjacent(nbrs[a], nbrs[b]) {
t += 1;
}
}
}
2.0 * t as f64 / (k * (k - 1)) as f64
}
pub fn average_clustering<G: GraphAdapter>(g: G) -> f64 {
let snap = Snapshot::new(g);
let n = snap.len();
if n == 0 {
return 0.0;
}
let sum: f64 = (0..n)
.map(|u| {
let k = snap.neighbors(u).len();
if k < 2 {
return 0.0;
}
let nbrs = snap.neighbors(u);
let mut t = 0usize;
for a in 0..nbrs.len() {
for b in (a + 1)..nbrs.len() {
if snap.adjacent(nbrs[a], nbrs[b]) {
t += 1;
}
}
}
2.0 * t as f64 / (k * (k - 1)) as f64
})
.sum();
sum / n as f64
}
pub fn global_clustering<G: GraphAdapter>(g: G) -> f64 {
let snap = Snapshot::new(g);
let n = snap.len();
let mut tri_sum = 0usize; let mut triplets = 0usize; for u in 0..n {
let k = snap.neighbors(u).len();
triplets += k.saturating_sub(1) * k / 2;
let nbrs = snap.neighbors(u);
for a in 0..nbrs.len() {
for b in (a + 1)..nbrs.len() {
if snap.adjacent(nbrs[a], nbrs[b]) {
tri_sum += 1;
}
}
}
}
if triplets == 0 {
return 0.0;
}
tri_sum as f64 / triplets as f64
}
pub fn degree_assortativity<G: GraphAdapter>(g: G) -> f64 {
let snap = Snapshot::new(g);
let n = snap.len();
let mut edges: Vec<(f64, f64)> = Vec::new();
for u in 0..n {
let ju = snap.neighbors(u).len() as f64;
for &v in snap.neighbors(u) {
if v > u {
let kv = snap.neighbors(v).len() as f64;
edges.push((ju, kv));
}
}
}
let m = edges.len() as f64;
if m == 0.0 {
return f64::NAN;
}
let s1: f64 = edges.iter().map(|&(j, k)| (j + k) / 2.0).sum::<f64>() / m;
let s2: f64 = edges
.iter()
.map(|&(j, k)| (j * j + k * k) / 2.0)
.sum::<f64>()
/ m;
let s3: f64 = edges.iter().map(|&(j, k)| j * k).sum::<f64>() / m;
let denom = s2 - s1 * s1;
if denom == 0.0 {
return f64::NAN;
}
(s3 - s1 * s1) / denom
}
pub fn rich_club<G: GraphAdapter>(g: G, k: usize) -> f64 {
let snap = Snapshot::new(g);
let n = snap.len();
let rich: Vec<usize> = (0..n).filter(|&u| snap.neighbors(u).len() > k).collect();
let nr = rich.len();
if nr < 2 {
return 0.0;
}
let mut e_rich = 0usize;
for a in 0..rich.len() {
for b in (a + 1)..rich.len() {
if snap.adjacent(rich[a], rich[b]) {
e_rich += 1;
}
}
}
2.0 * e_rich as f64 / (nr * (nr - 1)) as f64
}
pub fn rich_club_curve<G: GraphAdapter>(g: G) -> Vec<(usize, f64)> {
let snap = Snapshot::new(g);
let n = snap.len();
if n == 0 {
return Vec::new();
}
let max_deg = (0..n).map(|u| snap.neighbors(u).len()).max().unwrap_or(0);
(0..=max_deg)
.map(|k| {
let rich: Vec<usize> = (0..n).filter(|&u| snap.neighbors(u).len() > k).collect();
let nr = rich.len();
if nr < 2 {
return (k, 0.0);
}
let mut e_rich = 0usize;
for a in 0..rich.len() {
for b in (a + 1)..rich.len() {
if snap.adjacent(rich[a], rich[b]) {
e_rich += 1;
}
}
}
(k, 2.0 * e_rich as f64 / (nr * (nr - 1)) as f64)
})
.collect()
}
pub fn census_triangle_count<G: GraphAdapter>(g: G) -> usize {
let sel = Selector::connected_k_subsets(3);
let census = count(g, &sel);
let tri_class = Pattern::triangle().class_id();
census.get(&tri_class).copied().unwrap_or(0) as usize
}