use std::collections::HashMap;
use crate::canonical::{canonical_by, perms, ClassId};
use crate::snapshot::{GraphAdapter, Snapshot};
#[derive(Clone, Debug)]
pub struct Instance<N> {
pub nodes: Vec<N>,
pub class: ClassId,
}
pub const MAX_K: usize = 11;
#[derive(Clone, Copy, Debug)]
pub struct Selector {
k: usize,
}
impl Selector {
#[must_use]
pub fn connected_k_subsets(k: usize) -> Self {
assert!(
(2..=MAX_K).contains(&k),
"graphlet order k must be in 2..={MAX_K}, got {k}"
);
Selector { k }
}
#[inline]
#[must_use]
pub fn k(&self) -> usize {
self.k
}
}
pub type Census = HashMap<ClassId, u64>;
struct Frame {
ext: Vec<usize>,
v: usize,
}
pub struct Instances<N> {
snapshot: Snapshot<N>,
k: usize,
ps: Vec<Vec<usize>>,
next_root: usize,
frames: Vec<Frame>,
sub: Vec<usize>,
}
impl<N: Copy> Instances<N> {
fn new(snapshot: Snapshot<N>, k: usize) -> Self {
Instances {
k,
ps: perms(k),
next_root: 0,
frames: Vec::new(),
sub: Vec::new(),
snapshot,
}
}
fn extend(&self, sub: &[usize], w: usize, v: usize, mut ext: Vec<usize>) -> Vec<usize> {
for &u in self.snapshot.neighbors(w) {
if u <= v || sub.contains(&u) || ext.contains(&u) {
continue;
}
if !sub.iter().any(|&s| self.snapshot.adjacent(s, u)) {
ext.push(u);
}
}
ext
}
fn class_of(&self, sub: &[usize]) -> ClassId {
ClassId(canonical_by(self.k, &self.ps, |i, j| {
self.snapshot.adjacent(sub[i], sub[j])
}))
}
}
impl<N: Copy> Iterator for Instances<N> {
type Item = Instance<N>;
fn next(&mut self) -> Option<Instance<N>> {
loop {
if self.frames.is_empty() {
if self.next_root >= self.snapshot.len() {
return None;
}
let v = self.next_root;
self.next_root += 1;
let ext: Vec<usize> = self
.snapshot
.neighbors(v)
.iter()
.copied()
.filter(|&u| u > v)
.collect();
self.sub.clear();
self.sub.push(v);
self.frames.push(Frame { ext, v });
continue;
}
let last = self.frames.len() - 1;
let Some(w) = self.frames[last].ext.pop() else {
self.frames.pop();
self.sub.pop();
continue;
};
let v = self.frames[last].v;
if self.sub.len() + 1 == self.k {
self.sub.push(w);
let class = self.class_of(&self.sub);
let nodes = self.sub.iter().map(|&i| self.snapshot.id(i)).collect();
self.sub.pop();
return Some(Instance { nodes, class });
}
let remaining = self.frames[last].ext.clone();
let child_ext = self.extend(&self.sub, w, v, remaining);
self.sub.push(w);
self.frames.push(Frame { ext: child_ext, v });
}
}
}
#[must_use]
pub fn enumerate<G>(g: G, sel: &Selector) -> Instances<G::NodeId>
where
G: GraphAdapter,
{
Instances::new(Snapshot::new(g), sel.k)
}
pub(crate) fn for_each_subset<N: Copy>(
snapshot: &Snapshot<N>,
k: usize,
mut f: impl FnMut(&[usize]),
) {
fn ext_of<N: Copy>(
s: &Snapshot<N>,
sub: &[usize],
w: usize,
v: usize,
ext: &[usize],
) -> Vec<usize> {
let mut e = ext.to_vec();
for &u in s.neighbors(w) {
if u <= v || sub.contains(&u) || e.contains(&u) {
continue;
}
if !sub.iter().any(|&x| s.adjacent(x, u)) {
e.push(u);
}
}
e
}
fn rec<N: Copy>(
s: &Snapshot<N>,
sub: &mut Vec<usize>,
ext: &mut Vec<usize>,
v: usize,
k: usize,
f: &mut impl FnMut(&[usize]),
) {
if sub.len() == k {
f(sub);
return;
}
while let Some(w) = ext.pop() {
let mut child = ext_of(s, sub, w, v, ext);
sub.push(w);
rec(s, sub, &mut child, v, k, f);
sub.pop();
}
}
for v in 0..snapshot.len() {
let mut sub = vec![v];
let mut ext: Vec<usize> = snapshot
.neighbors(v)
.iter()
.copied()
.filter(|&u| u > v)
.collect();
rec(snapshot, &mut sub, &mut ext, v, k, &mut f);
}
}
#[must_use]
pub fn count<G>(g: G, sel: &Selector) -> Census
where
G: GraphAdapter,
{
let snapshot = Snapshot::new(g);
let ps = perms(sel.k);
let mut census: Census = HashMap::new();
for_each_subset(&snapshot, sel.k, |sub| {
let class = ClassId(canonical_by(sel.k, &ps, |i, j| {
snapshot.adjacent(sub[i], sub[j])
}));
*census.entry(class).or_insert(0) += 1;
});
census
}