use std::collections::{BTreeMap, HashMap};
use crate::canonical::{all_connected_classes, canonical_arg_by, class_to_adj, mask_of_by, perms};
use crate::census::for_each_subset;
use crate::snapshot::{GraphAdapter, Snapshot};
const MAX_K: usize = 5;
#[derive(Clone, Debug)]
pub struct Registry {
slot_global: HashMap<(usize, u64), Vec<usize>>,
orbit_info: Vec<(usize, u64, usize)>,
}
impl Registry {
#[must_use]
pub fn build() -> Self {
let mut slot_global = HashMap::new();
let mut orbit_info: Vec<(usize, u64, usize)> = Vec::new();
let mut next_id = 0usize;
for k in 2..=MAX_K {
for class in all_connected_classes(k) {
let part = orbit_partition(class, k);
let n_local = part.iter().copied().max().unwrap() + 1;
let mut sizes = vec![0usize; n_local];
for &o in &part {
sizes[o] += 1;
}
let base = next_id;
let slotmap: Vec<usize> = part.iter().map(|&lo| base + lo).collect();
for &sz in &sizes {
orbit_info.push((k, class, sz));
}
next_id += n_local;
slot_global.insert((k, class), slotmap);
}
}
Registry {
slot_global,
orbit_info,
}
}
#[inline]
#[must_use]
pub fn orbit_count(&self) -> usize {
self.orbit_info.len()
}
#[inline]
#[must_use]
pub fn orbit_meta(&self, id: usize) -> (usize, u64, usize) {
self.orbit_info[id]
}
pub(crate) fn slot_ids(&self, k: usize, class: u64) -> &[usize] {
&self.slot_global[&(k, class)]
}
#[cfg(test)]
pub(crate) fn slot_map(&self, k: usize, class: u64) -> &[usize] {
self.slot_ids(k, class)
}
}
fn orbit_partition(class: u64, k: usize) -> Vec<usize> {
let adj = class_to_adj(class, k);
let is_adj = |a: usize, b: usize| adj[a].contains(&b);
let ps = perms(k);
let autos: Vec<&Vec<usize>> = ps
.iter()
.filter(|p| mask_of_by(k, p, is_adj) == class)
.collect();
let mut parent: Vec<usize> = (0..k).collect();
fn find(parent: &mut [usize], x: usize) -> usize {
if parent[x] != x {
let r = find(parent, parent[x]);
parent[x] = r;
}
parent[x]
}
for p in &autos {
for (i, &pi) in p.iter().enumerate() {
let a = find(&mut parent, i);
let b = find(&mut parent, pi);
if a != b {
parent[a] = b;
}
}
}
let mut root_to_orbit: BTreeMap<usize, usize> = BTreeMap::new();
let mut slot_orbit = vec![0usize; k];
for (i, slot) in slot_orbit.iter_mut().enumerate() {
let r = find(&mut parent, i);
let n = root_to_orbit.len();
*slot = *root_to_orbit.entry(r).or_insert(n);
}
slot_orbit
}
#[derive(Clone, Debug)]
pub struct GdvTable<N> {
orbit_count: usize,
ids: Vec<N>,
rows: Vec<Vec<u64>>,
}
impl<N> GdvTable<N> {
pub(crate) fn from_parts(orbit_count: usize, ids: Vec<N>, rows: Vec<Vec<u64>>) -> Self {
GdvTable {
orbit_count,
ids,
rows,
}
}
}
impl<N: Copy> GdvTable<N> {
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.ids.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
#[inline]
#[must_use]
pub fn orbit_count(&self) -> usize {
self.orbit_count
}
#[inline]
#[must_use]
pub fn id(&self, i: usize) -> N {
self.ids[i]
}
#[inline]
#[must_use]
pub fn row(&self, i: usize) -> &[u64] {
&self.rows[i]
}
pub fn iter(&self) -> impl Iterator<Item = (N, &[u64])> {
self.ids
.iter()
.copied()
.zip(self.rows.iter().map(Vec::as_slice))
}
#[must_use]
pub fn degree_distribution(&self, orbit: usize) -> BTreeMap<u64, u64> {
let mut dist = BTreeMap::new();
for row in &self.rows {
*dist.entry(row[orbit]).or_insert(0) += 1;
}
dist
}
}
#[must_use]
pub fn graphlet_degree_vectors<G>(g: G, reg: &Registry) -> GdvTable<G::NodeId>
where
G: GraphAdapter,
{
let snapshot = Snapshot::new(g);
let n = snapshot.len();
let mut rows = vec![vec![0u64; reg.orbit_count()]; n];
for k in 2..=MAX_K {
let ps = perms(k);
for_each_subset(&snapshot, k, |sub| {
let (class, arg) = canonical_arg_by(k, &ps, |i, j| snapshot.adjacent(sub[i], sub[j]));
let slotmap = ®.slot_global[&(k, class)];
for (c, &slot) in slotmap.iter().enumerate() {
rows[sub[arg[c]]][slot] += 1;
}
});
}
let ids = (0..n).map(|i| snapshot.id(i)).collect();
GdvTable {
orbit_count: reg.orbit_count(),
ids,
rows,
}
}