use crate::algo::GraphProjection;
use crate::algo::algorithms::Algorithm;
use std::collections::HashSet;
use uni_common::core::id::Vid;
pub struct MaximalCliques;
#[derive(Debug, Clone)]
pub struct MaximalCliquesConfig {
pub min_size: usize,
}
impl Default for MaximalCliquesConfig {
fn default() -> Self {
Self { min_size: 2 }
}
}
pub struct MaximalCliquesResult {
pub cliques: Vec<Vec<Vid>>,
}
impl Algorithm for MaximalCliques {
type Config = MaximalCliquesConfig;
type Result = MaximalCliquesResult;
fn name() -> &'static str {
"maximal_cliques"
}
fn run(graph: &GraphProjection, config: Self::Config) -> Self::Result {
let n = graph.vertex_count();
if n == 0 {
return MaximalCliquesResult {
cliques: Vec::new(),
};
}
let mut adj = vec![HashSet::new(); n];
for u in 0..n {
for &v_u32 in graph.out_neighbors(u as u32) {
let v = v_u32 as usize;
if u != v {
adj[u].insert(v);
adj[v].insert(u);
}
}
if graph.has_reverse() {
for &v_u32 in graph.in_neighbors(u as u32) {
let v = v_u32 as usize;
if u != v {
adj[u].insert(v);
adj[v].insert(u);
}
}
}
}
let mut cliques = Vec::new();
let r = HashSet::new();
let p: HashSet<usize> = (0..n).collect();
let x = HashSet::new();
bron_kerbosch_pivot(&adj, r, p, x, config.min_size, &mut cliques);
let mapped_cliques = cliques
.into_iter()
.map(|clique| {
clique
.into_iter()
.map(|idx| graph.to_vid(idx as u32))
.collect()
})
.collect();
MaximalCliquesResult {
cliques: mapped_cliques,
}
}
}
fn bron_kerbosch_pivot(
adj: &[HashSet<usize>],
r: HashSet<usize>,
mut p: HashSet<usize>,
mut x: HashSet<usize>,
min_size: usize,
cliques: &mut Vec<Vec<usize>>,
) {
if p.is_empty() && x.is_empty() {
if r.len() >= min_size {
cliques.push(r.into_iter().collect());
}
return;
}
let u_pivot = p
.union(&x)
.max_by_key(|&u| p.iter().filter(|&v| adj[*u].contains(v)).count())
.copied();
let u = u_pivot.unwrap();
let candidates: Vec<usize> = p.iter().filter(|&v| !adj[u].contains(v)).copied().collect();
for v in candidates {
let mut new_r = r.clone();
new_r.insert(v);
let new_p: HashSet<usize> = p.iter().filter(|&n| adj[v].contains(n)).copied().collect();
let new_x: HashSet<usize> = x.iter().filter(|&n| adj[v].contains(n)).copied().collect();
bron_kerbosch_pivot(adj, new_r, new_p, new_x, min_size, cliques);
p.remove(&v);
x.insert(v);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algo::test_utils::build_test_graph;
#[test]
fn test_cliques_triangle() {
let vids = vec![Vid::from(0), Vid::from(1), Vid::from(2)];
let edges = vec![
(Vid::from(0), Vid::from(1)),
(Vid::from(1), Vid::from(2)),
(Vid::from(2), Vid::from(0)),
];
let graph = build_test_graph(vids, edges);
let result = MaximalCliques::run(&graph, MaximalCliquesConfig::default());
assert_eq!(result.cliques.len(), 1);
assert_eq!(result.cliques[0].len(), 3);
}
#[test]
fn test_cliques_two_triangles() {
let vids = (0..5).map(Vid::from).collect();
let edges = vec![
(Vid::from(0), Vid::from(1)),
(Vid::from(1), Vid::from(2)),
(Vid::from(2), Vid::from(0)),
(Vid::from(2), Vid::from(3)),
(Vid::from(3), Vid::from(4)),
(Vid::from(4), Vid::from(2)),
];
let graph = build_test_graph(vids, edges);
let result = MaximalCliques::run(&graph, MaximalCliquesConfig::default());
assert_eq!(result.cliques.len(), 2);
}
}